chromium/components/exo/wayland/xdg_shell.cc

// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/exo/wayland/xdg_shell.h"

#include <wayland-server-core.h>
#include <wayland-server-protocol-core.h>
#include <xdg-decoration-unstable-v1-server-protocol.h>
#include <xdg-shell-server-protocol.h>

#include <optional>

#include "ash/public/cpp/shell_window_ids.h"
#include "ash/public/cpp/window_properties.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/utf_string_conversions.h"
#include "chromeos/ui/base/window_state_type.h"
#include "components/exo/display.h"
#include "components/exo/shell_surface_util.h"
#include "components/exo/wayland/serial_tracker.h"
#include "components/exo/wayland/server_util.h"
#include "components/exo/wayland/wayland_display_observer.h"
#include "components/exo/wayland/wayland_positioner.h"
#include "components/exo/xdg_shell_surface.h"
#include "ui/aura/window_observer.h"
#include "ui/base/hit_test.h"
#include "ui/display/screen.h"
#include "ui/display/types/display_constants.h"
#include "ui/views/widget/widget.h"
#include "ui/wm/core/coordinate_conversion.h"

namespace exo {
namespace wayland {
namespace {

////////////////////////////////////////////////////////////////////////////////
// xdg_positioner_interface:

void xdg_positioner_destroy(wl_client* client, wl_resource* resource) {
  wl_resource_destroy(resource);
}

void xdg_positioner_set_size(wl_client* client,
                             wl_resource* resource,
                             int32_t width,
                             int32_t height) {
  if (width < 1 || height < 1) {
    wl_resource_post_error(resource, XDG_POSITIONER_ERROR_INVALID_INPUT,
                           "width and height must be positive and non-zero");
    return;
  }

  GetUserDataAs<WaylandPositioner>(resource)->SetSize(gfx::Size(width, height));
}

void xdg_positioner_set_anchor_rect(wl_client* client,
                                    wl_resource* resource,
                                    int32_t x,
                                    int32_t y,
                                    int32_t width,
                                    int32_t height) {
  if (width < 1 || height < 1) {
    wl_resource_post_error(resource, XDG_POSITIONER_ERROR_INVALID_INPUT,
                           "width and height must be positive and non-zero");
    return;
  }

  GetUserDataAs<WaylandPositioner>(resource)->SetAnchorRect(
      gfx::Rect(x, y, width, height));
}

void xdg_positioner_set_anchor(wl_client* client,
                               wl_resource* resource,
                               uint32_t anchor) {
  GetUserDataAs<WaylandPositioner>(resource)->SetAnchor(anchor);
}

void xdg_positioner_set_gravity(wl_client* client,
                                wl_resource* resource,
                                uint32_t gravity) {
  GetUserDataAs<WaylandPositioner>(resource)->SetGravity(gravity);
}

void xdg_positioner_set_constraint_adjustment(wl_client* client,
                                              wl_resource* resource,
                                              uint32_t adjustment) {
  GetUserDataAs<WaylandPositioner>(resource)->SetAdjustment(adjustment);
}

void xdg_positioner_set_offset(wl_client* client,
                               wl_resource* resource,
                               int32_t x,
                               int32_t y) {
  GetUserDataAs<WaylandPositioner>(resource)->SetOffset(gfx::Vector2d(x, y));
}

// Version 3 xdg_positioner
// Empty since Weston doesn't implement this.
// Ref:
// https://gitlab.freedesktop.org/wayland/weston/-/blob/main/libweston/desktop/xdg-shell.c#L312
void xdg_positioner_set_reactive(wl_client* client, wl_resource* resource) {}

void xdg_positioner_set_parent_size(wl_client* client,
                                    wl_resource* resource,
                                    int32_t parent_width,
                                    int32_t parent_height) {}

void xdg_positioner_set_parent_configure(wl_client* client,
                                         wl_resource* resource,
                                         uint32_t serial) {}

const struct xdg_positioner_interface xdg_positioner_implementation = {
    xdg_positioner_destroy,         xdg_positioner_set_size,
    xdg_positioner_set_anchor_rect, xdg_positioner_set_anchor,
    xdg_positioner_set_gravity,     xdg_positioner_set_constraint_adjustment,
    xdg_positioner_set_offset,      xdg_positioner_set_reactive,
    xdg_positioner_set_parent_size, xdg_positioner_set_parent_configure};

////////////////////////////////////////////////////////////////////////////////
// xdg_toplevel_interface:

int XdgToplevelResizeComponent(uint32_t edges) {
  switch (edges) {
    case XDG_TOPLEVEL_RESIZE_EDGE_TOP:
      return HTTOP;
    case XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM:
      return HTBOTTOM;
    case XDG_TOPLEVEL_RESIZE_EDGE_LEFT:
      return HTLEFT;
    case XDG_TOPLEVEL_RESIZE_EDGE_TOP_LEFT:
      return HTTOPLEFT;
    case XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_LEFT:
      return HTBOTTOMLEFT;
    case XDG_TOPLEVEL_RESIZE_EDGE_RIGHT:
      return HTRIGHT;
    case XDG_TOPLEVEL_RESIZE_EDGE_TOP_RIGHT:
      return HTTOPRIGHT;
    case XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_RIGHT:
      return HTBOTTOMRIGHT;
    default:
      return HTBOTTOMRIGHT;
  }
}

using XdgSurfaceConfigureCallback = base::RepeatingCallback<void(
    const gfx::Size& size,
    chromeos::WindowStateType state_type,
    bool resizing,
    bool activated,
    std::optional<chromeos::WindowStateType> restore_state_type)>;

uint32_t HandleXdgSurfaceConfigureCallback(
    wl_resource* resource,
    SerialTracker* serial_tracker,
    const XdgSurfaceConfigureCallback& callback,
    const gfx::Rect& bounds,
    chromeos::WindowStateType state_type,
    bool resizing,
    bool activated,
    const gfx::Vector2d& origin_offset,
    float raster_scale,
    aura::Window::OcclusionState occlusion_state,
    std::optional<chromeos::WindowStateType> restore_state_type) {
  uint32_t serial =
      serial_tracker->GetNextSerial(SerialTracker::EventType::OTHER_EVENT);
  callback.Run(bounds.size(), state_type, resizing, activated,
               restore_state_type);
  xdg_surface_send_configure(resource, serial);
  wl_client_flush(wl_resource_get_client(resource));
  return serial;
}

// Wrapper around shell surface that allows us to handle the case where the
// xdg surface resource is destroyed before the toplevel resource.
class WaylandToplevel : public aura::WindowObserver {
 public:
  WaylandToplevel(wl_resource* xdg_toplevel_resource,
                  wl_resource* xdg_surface_resource)
      : xdg_toplevel_resource_(xdg_toplevel_resource),
        xdg_surface_resource_(xdg_surface_resource),
        shell_surface_data_(
            GetUserDataAs<WaylandXdgSurface>(xdg_surface_resource)) {
    shell_surface_data_->shell_surface->host_window()->AddObserver(this);
    shell_surface_data_->shell_surface->set_close_callback(base::BindRepeating(
        &WaylandToplevel::OnClose, weak_ptr_factory_.GetWeakPtr()));
    shell_surface_data_->shell_surface->set_configure_callback(
        base::BindRepeating(
            &HandleXdgSurfaceConfigureCallback, xdg_surface_resource,
            shell_surface_data_->serial_tracker,
            base::BindRepeating(&WaylandToplevel::OnConfigure,
                                weak_ptr_factory_.GetWeakPtr())));
  }

  WaylandToplevel(const WaylandToplevel&) = delete;
  WaylandToplevel& operator=(const WaylandToplevel&) = delete;

  ~WaylandToplevel() override {
    if (shell_surface_data_)
      shell_surface_data_->shell_surface->host_window()->RemoveObserver(this);
  }

  // Overridden from aura::WindowObserver:
  void OnWindowDestroying(aura::Window* window) override {
    window->RemoveObserver(this);
    shell_surface_data_ = nullptr;
  }

  void SetParent(WaylandToplevel* parent) {
    if (!shell_surface_data_)
      return;

    if (!parent) {
      shell_surface_data_->shell_surface->SetParent(nullptr);
      return;
    }

    // This is a no-op if parent is not mapped.
    if (parent->shell_surface_data_ &&
        parent->shell_surface_data_->shell_surface->GetWidget())
      shell_surface_data_->shell_surface->SetParent(
          parent->shell_surface_data_->shell_surface.get());
  }

  void SetTitle(const std::u16string& title) {
    if (shell_surface_data_)
      shell_surface_data_->shell_surface->SetTitle(title);
  }

  void SetApplicationId(const char* application_id) {
    if (shell_surface_data_)
      shell_surface_data_->shell_surface->SetApplicationId(application_id);
  }

  void Move() {
    if (shell_surface_data_)
      shell_surface_data_->shell_surface->StartMove();
  }

  void Resize(int component) {
    if (!shell_surface_data_)
      return;

    if (component != HTNOWHERE)
      shell_surface_data_->shell_surface->StartResize(component);
  }

  void SetMaximumSize(const gfx::Size& size) {
    if (shell_surface_data_)
      shell_surface_data_->shell_surface->SetMaximumSize(size);
  }

  void SetMinimumSize(const gfx::Size& size) {
    if (shell_surface_data_)
      shell_surface_data_->shell_surface->SetMinimumSize(size);
  }

  void Maximize() {
    if (shell_surface_data_)
      shell_surface_data_->shell_surface->Maximize();
  }

  void Restore() {
    if (shell_surface_data_)
      shell_surface_data_->shell_surface->Restore();
  }

  void SetFullscreen(bool fullscreen,
                     int64_t display_id = display::kInvalidDisplayId) {
    if (shell_surface_data_)
      shell_surface_data_->shell_surface->SetFullscreen(fullscreen, display_id);
  }

  void Minimize() {
    if (shell_surface_data_)
      shell_surface_data_->shell_surface->Minimize();
  }

  void SetFrame(SurfaceFrameType type) {
    if (shell_surface_data_)
      shell_surface_data_->shell_surface->OnSetFrame(type);
  }

  ShellSurfaceData GetShellSurfaceData() {
    return ShellSurfaceData(shell_surface_data_->shell_surface.get(),
                            shell_surface_data_->serial_tracker,
                            shell_surface_data_->rotation_serial_tracker,
                            xdg_surface_resource_);
  }

 private:
  void OnClose() {
    xdg_toplevel_send_close(xdg_toplevel_resource_);
    wl_client_flush(wl_resource_get_client(xdg_toplevel_resource_));
  }

  static void AddState(wl_array* states, xdg_toplevel_state state) {
    xdg_toplevel_state* value = static_cast<xdg_toplevel_state*>(
        wl_array_add(states, sizeof(xdg_toplevel_state)));
    DCHECK(value);
    *value = state;
  }

  void OnConfigure(
      const gfx::Size& size,
      chromeos::WindowStateType state_type,
      bool resizing,
      bool activated,
      std::optional<chromeos::WindowStateType> restore_state_type) {
    wl_array states;
    wl_array_init(&states);
    if (state_type == chromeos::WindowStateType::kMaximized)
      AddState(&states, XDG_TOPLEVEL_STATE_MAXIMIZED);
    // TODO(crbug.com/40197882): Pinned states need to be handled properly.
    if (IsFullscreenOrPinnedWindowStateType(state_type)) {
      AddState(&states, XDG_TOPLEVEL_STATE_FULLSCREEN);
      // If the window was maxmized before it is fullscreened, we should
      // keep this state while it is fullscreened. This is what X11 apps, and
      // thus standard wayland apps expect, and they may rely on this behavior
      // even though this is not explicitly specified in the protocol spec.
      if (restore_state_type.has_value() &&
          restore_state_type.value() == chromeos::WindowStateType::kMaximized) {
        AddState(&states, XDG_TOPLEVEL_STATE_MAXIMIZED);
      }
    }
    if (resizing)
      AddState(&states, XDG_TOPLEVEL_STATE_RESIZING);
    if (activated)
      AddState(&states, XDG_TOPLEVEL_STATE_ACTIVATED);
    xdg_toplevel_send_configure(xdg_toplevel_resource_, size.width(),
                                size.height(), &states);
    wl_array_release(&states);
  }

  const raw_ptr<wl_resource> xdg_toplevel_resource_;
  const raw_ptr<wl_resource, DanglingUntriaged> xdg_surface_resource_;
  raw_ptr<WaylandXdgSurface> shell_surface_data_;
  base::WeakPtrFactory<WaylandToplevel> weak_ptr_factory_{this};
};

void xdg_toplevel_destroy(wl_client* client, wl_resource* resource) {
  wl_resource_destroy(resource);
}

void xdg_toplevel_set_parent(wl_client* client,
                             wl_resource* resource,
                             wl_resource* parent) {
  WaylandToplevel* parent_surface = nullptr;
  if (parent)
    parent_surface = GetUserDataAs<WaylandToplevel>(parent);

  GetUserDataAs<WaylandToplevel>(resource)->SetParent(parent_surface);
}

void xdg_toplevel_set_title(wl_client* client,
                            wl_resource* resource,
                            const char* title) {
  GetUserDataAs<WaylandToplevel>(resource)->SetTitle(
      std::u16string(base::UTF8ToUTF16(title)));
}

void xdg_toplevel_set_app_id(wl_client* client,
                             wl_resource* resource,
                             const char* app_id) {
  GetUserDataAs<WaylandToplevel>(resource)->SetApplicationId(app_id);
}

void xdg_toplevel_show_window_menu(wl_client* client,
                                   wl_resource* resource,
                                   wl_resource* seat,
                                   uint32_t serial,
                                   int32_t x,
                                   int32_t y) {
  NOTIMPLEMENTED();
}

void xdg_toplevel_move(wl_client* client,
                       wl_resource* resource,
                       wl_resource* seat,
                       uint32_t serial) {
  GetUserDataAs<WaylandToplevel>(resource)->Move();
}

void xdg_toplevel_resize(wl_client* client,
                         wl_resource* resource,
                         wl_resource* seat,
                         uint32_t serial,
                         uint32_t edges) {
  GetUserDataAs<WaylandToplevel>(resource)->Resize(
      XdgToplevelResizeComponent(edges));
}

void xdg_toplevel_set_max_size(wl_client* client,
                               wl_resource* resource,
                               int32_t width,
                               int32_t height) {
  GetUserDataAs<WaylandToplevel>(resource)->SetMaximumSize(
      gfx::Size(width, height));
}

void xdg_toplevel_set_min_size(wl_client* client,
                               wl_resource* resource,
                               int32_t width,
                               int32_t height) {
  GetUserDataAs<WaylandToplevel>(resource)->SetMinimumSize(
      gfx::Size(width, height));
}

void xdg_toplevel_set_maximized(wl_client* client, wl_resource* resource) {
  GetUserDataAs<WaylandToplevel>(resource)->Maximize();
}

void xdg_toplevel_unset_maximized(wl_client* client, wl_resource* resource) {
  GetUserDataAs<WaylandToplevel>(resource)->Restore();
}

void xdg_toplevel_set_fullscreen(wl_client* client,
                                 wl_resource* resource,
                                 wl_resource* output) {
  int64_t display_id = output
                           ? GetUserDataAs<WaylandDisplayHandler>(output)->id()
                           : display::kInvalidDisplayId;
  GetUserDataAs<WaylandToplevel>(resource)->SetFullscreen(true, display_id);
}

void xdg_toplevel_unset_fullscreen(wl_client* client, wl_resource* resource) {
  GetUserDataAs<WaylandToplevel>(resource)->SetFullscreen(false);
}

void xdg_toplevel_set_minimized(wl_client* client, wl_resource* resource) {
  GetUserDataAs<WaylandToplevel>(resource)->Minimize();
}

const struct xdg_toplevel_interface xdg_toplevel_implementation = {
    xdg_toplevel_destroy,          xdg_toplevel_set_parent,
    xdg_toplevel_set_title,        xdg_toplevel_set_app_id,
    xdg_toplevel_show_window_menu, xdg_toplevel_move,
    xdg_toplevel_resize,           xdg_toplevel_set_max_size,
    xdg_toplevel_set_min_size,     xdg_toplevel_set_maximized,
    xdg_toplevel_unset_maximized,  xdg_toplevel_set_fullscreen,
    xdg_toplevel_unset_fullscreen, xdg_toplevel_set_minimized};

class WaylandXdgToplevelDecoration {
 public:
  WaylandXdgToplevelDecoration(wl_resource* resource,
                               wl_resource* toplevel_resource)
      : resource_(resource),
        top_level_(GetUserDataAs<WaylandToplevel>(toplevel_resource)) {}

  WaylandXdgToplevelDecoration(const WaylandXdgToplevelDecoration&) = delete;
  WaylandXdgToplevelDecoration& operator=(const WaylandXdgToplevelDecoration&) =
      delete;

  uint32_t decoration_mode() const { return default_mode_; }
  void SetDecorationMode(uint32_t mode) {
    if (default_mode_ != mode) {
      default_mode_ = mode;
      OnConfigure(mode);
    }
  }

 private:
  void OnConfigure(uint32_t mode) {
    switch (mode) {
      case ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE:
        top_level_->SetFrame(SurfaceFrameType::NONE);
        break;
      case ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE:
        top_level_->SetFrame(SurfaceFrameType::NORMAL);
        break;
    }
    zxdg_toplevel_decoration_v1_send_configure(resource_, mode);
  }

  const raw_ptr<wl_resource> resource_;
  raw_ptr<WaylandToplevel, DanglingUntriaged> top_level_;
  // Keeps track of the xdg-decoration mode on server side.
  uint32_t default_mode_ = ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE;
};

////////////////////////////////////////////////////////////////////////////////
// xdg_popup_interface:

// Wrapper around shell surface that allows us to handle the case where the
// xdg surface resource is destroyed before the popup resource.
class WaylandPopup : aura::WindowObserver {
 public:
  WaylandPopup(wl_resource* resource, wl_resource* surface_resource)
      : resource_(resource),
        surface_resource_(surface_resource),
        shell_surface_data_(
            GetUserDataAs<WaylandXdgSurface>(surface_resource)) {
    shell_surface_data_->shell_surface->host_window()->AddObserver(this);
    shell_surface_data_->shell_surface->set_close_callback(base::BindRepeating(
        &WaylandPopup::OnClose, weak_ptr_factory_.GetWeakPtr()));
    shell_surface_data_->shell_surface->set_configure_callback(
        base::BindRepeating(
            &HandleXdgSurfaceConfigureCallback, surface_resource,
            shell_surface_data_->serial_tracker,
            base::BindRepeating(&WaylandPopup::OnConfigure,
                                weak_ptr_factory_.GetWeakPtr())));
  }

  WaylandPopup(const WaylandPopup&) = delete;
  WaylandPopup& operator=(const WaylandPopup&) = delete;

  ~WaylandPopup() override {
    if (shell_surface_data_)
      shell_surface_data_->shell_surface->host_window()->RemoveObserver(this);
  }

  ShellSurfaceBase* GetShellSurface() {
    return shell_surface_data_->shell_surface.get();
  }

  void Grab() {
    if (!shell_surface_data_) {
      wl_resource_post_error(resource_.get(), XDG_POPUP_ERROR_INVALID_GRAB,
                             "the surface has already been destroyed");
      return;
    }
    if (shell_surface_data_->shell_surface->GetWidget()) {
      wl_resource_post_error(resource_.get(), XDG_POPUP_ERROR_INVALID_GRAB,
                             "grab must be called before construction");
      return;
    }
    shell_surface_data_->shell_surface->Grab();
  }

  void Reposition(WaylandPositioner* positioner, uint32_t token) {
    if (wl_resource_get_version(resource_) <
        XDG_POPUP_REPOSITIONED_SINCE_VERSION) {
      return;
    }
    xdg_popup_send_repositioned(resource_, token);

    display::Display display =
        display::Screen::GetScreen()->GetDisplayNearestWindow(
            shell_surface_data_->shell_surface->GetWidget()
                ->parent()
                ->GetNativeWindow());
    gfx::Rect work_area = display.work_area();
    wm::ConvertRectFromScreen(shell_surface_data_->shell_surface->GetWidget()
                                  ->parent()
                                  ->GetNativeWindow(),
                              &work_area);
    WaylandPositioner::Result position = positioner->CalculateBounds(work_area);
    gfx::Point origin = position.origin;
    views::View::ConvertPointToScreen(
        shell_surface_data_->shell_surface->GetWidget()
            ->parent()
            ->widget_delegate()
            ->GetContentsView(),
        &origin);

    shell_surface_data_->shell_surface->SetOrigin(origin);
    shell_surface_data_->shell_surface->SetSize(position.size);

    xdg_popup_send_configure(resource_, position.origin.x(),
                             position.origin.y(), position.size.width(),
                             position.size.height());
    xdg_surface_send_configure(
        surface_resource_, shell_surface_data_->serial_tracker->GetNextSerial(
                               SerialTracker::EventType::OTHER_EVENT));
  }

  // Overridden from aura::WindowObserver:
  void OnWindowDestroying(aura::Window* window) override {
    window->RemoveObserver(this);
    shell_surface_data_ = nullptr;
  }

 private:
  void OnClose() {
    xdg_popup_send_popup_done(resource_);
    wl_client_flush(wl_resource_get_client(resource_));
  }

  void OnConfigure(
      const gfx::Size& size,
      chromeos::WindowStateType state_type,
      bool resizing,
      bool activated,
      std::optional<chromeos::WindowStateType> restore_state_type) {
    // Nothing to do here as popups don't have additional configure state.
  }

  const raw_ptr<wl_resource> resource_;
  const raw_ptr<wl_resource, DanglingUntriaged> surface_resource_;
  raw_ptr<WaylandXdgSurface> shell_surface_data_;
  base::WeakPtrFactory<WaylandPopup> weak_ptr_factory_{this};
};

void xdg_popup_destroy(wl_client* client, wl_resource* resource) {
  wl_resource_destroy(resource);
}

void xdg_popup_grab(wl_client* client,
                    wl_resource* resource,
                    wl_resource* seat,
                    uint32_t serial) {
  GetUserDataAs<WaylandPopup>(resource)->Grab();
}

void xdg_popup_reposition(wl_client* client,
                          wl_resource* resource,
                          wl_resource* positioner,
                          uint32_t token) {
  GetUserDataAs<WaylandPopup>(resource)->Reposition(
      GetUserDataAs<WaylandPositioner>(positioner), token);
}

const struct xdg_popup_interface xdg_popup_implementation = {
    xdg_popup_destroy, xdg_popup_grab, xdg_popup_reposition};

////////////////////////////////////////////////////////////////////////////////
// xdg_surface_interface:

void xdg_surface_destroy(wl_client* client, wl_resource* resource) {
  wl_resource_destroy(resource);
}

void xdg_surface_get_toplevel(wl_client* client,
                              wl_resource* xdg_surface_resource,
                              uint32_t id) {
  auto* shell_surface_data =
      GetUserDataAs<WaylandXdgSurface>(xdg_surface_resource);
  if (shell_surface_data->shell_surface->GetEnabled()) {
    wl_resource_post_error(xdg_surface_resource,
                           XDG_SURFACE_ERROR_ALREADY_CONSTRUCTED,
                           "surface has already been constructed");
    return;
  }

  shell_surface_data->shell_surface->SetCanMinimize(true);
  shell_surface_data->shell_surface->SetEnabled(true);

  wl_resource* xdg_toplevel_resource =
      wl_resource_create(client, &xdg_toplevel_interface, 1, id);

  SetImplementation(xdg_toplevel_resource, &xdg_toplevel_implementation,
                    std::make_unique<WaylandToplevel>(xdg_toplevel_resource,
                                                      xdg_surface_resource));
}

void xdg_surface_get_popup(wl_client* client,
                           wl_resource* resource,
                           uint32_t id,
                           wl_resource* parent_resource,
                           wl_resource* positioner_resource) {
  auto* shell_surface_data = GetUserDataAs<WaylandXdgSurface>(resource);
  if (shell_surface_data->shell_surface->GetEnabled()) {
    wl_resource_post_error(resource, XDG_SURFACE_ERROR_ALREADY_CONSTRUCTED,
                           "surface has already been constructed");
    return;
  }

  if (!parent_resource) {
    wl_resource_post_error(resource, XDG_SURFACE_ERROR_NOT_CONSTRUCTED,
                           "popup parent not supplied");
    return;
  }

  auto* parent_data = GetUserDataAs<WaylandXdgSurface>(parent_resource);
  if (!parent_data->shell_surface->GetWidget()) {
    wl_resource_post_error(resource, XDG_SURFACE_ERROR_NOT_CONSTRUCTED,
                           "popup parent not constructed");
    return;
  }

  if (shell_surface_data->shell_surface->GetWidget()) {
    wl_resource_post_error(resource, XDG_SURFACE_ERROR_ALREADY_CONSTRUCTED,
                           "get_popup is called after constructed");
    return;
  }

  display::Display display =
      display::Screen::GetScreen()->GetDisplayNearestWindow(
          parent_data->shell_surface->GetWidget()->GetNativeWindow());
  gfx::Rect work_area = display.work_area();
  wm::ConvertRectFromScreen(
      parent_data->shell_surface->GetWidget()->GetNativeWindow(), &work_area);

  // Try layout using parent's flip state.
  WaylandPositioner* positioner =
      GetUserDataAs<WaylandPositioner>(positioner_resource);
  WaylandPositioner::Result position = positioner->CalculateBounds(work_area);

  // |position| is relative to the parent's contents view origin, and |origin|
  // is in screen coordinates.
  gfx::Point origin = position.origin;
  views::View::ConvertPointToScreen(parent_data->shell_surface->GetWidget()
                                        ->widget_delegate()
                                        ->GetContentsView(),
                                    &origin);
  shell_surface_data->shell_surface->SetOrigin(origin);
  shell_surface_data->shell_surface->SetSize(position.size);
  shell_surface_data->shell_surface->DisableMovement();
  shell_surface_data->shell_surface->SetActivatable(false);
  shell_surface_data->shell_surface->SetCanMinimize(false);
  shell_surface_data->shell_surface->SetParent(
      parent_data->shell_surface.get());
  shell_surface_data->shell_surface->SetPopup();
  shell_surface_data->shell_surface->SetEnabled(true);

  wl_resource* xdg_popup_resource = wl_resource_create(
      client, &xdg_popup_interface, wl_resource_get_version(resource), id);

  SetImplementation(
      xdg_popup_resource, &xdg_popup_implementation,
      std::make_unique<WaylandPopup>(xdg_popup_resource, resource));

  // We send the configure event here as this event needs x,y coordinates
  // relative to the parent window.
  xdg_popup_send_configure(xdg_popup_resource, position.origin.x(),
                           position.origin.y(), position.size.width(),
                           position.size.height());
}

void xdg_surface_set_window_geometry(wl_client* client,
                                     wl_resource* resource,
                                     int32_t x,
                                     int32_t y,
                                     int32_t width,
                                     int32_t height) {
  GetUserDataAs<WaylandXdgSurface>(resource)->shell_surface->SetGeometry(
      gfx::Rect(x, y, width, height));
}

void xdg_surface_ack_configure(wl_client* client,
                               wl_resource* resource,
                               uint32_t serial) {
  GetUserDataAs<WaylandXdgSurface>(resource)
      ->shell_surface->AcknowledgeConfigure(serial);
}

const struct xdg_surface_interface xdg_surface_implementation = {
    xdg_surface_destroy, xdg_surface_get_toplevel, xdg_surface_get_popup,
    xdg_surface_set_window_geometry, xdg_surface_ack_configure};

////////////////////////////////////////////////////////////////////////////////
// xdg_wm_base_interface:

void xdg_wm_base_destroy(wl_client* client, wl_resource* resource) {
  // Nothing to do here.
}

void xdg_wm_base_create_positioner(wl_client* client,
                                   wl_resource* resource,
                                   uint32_t id) {
  wl_resource* positioner_resource = wl_resource_create(
      client, &xdg_positioner_interface, wl_resource_get_version(resource), id);

  SetImplementation(positioner_resource, &xdg_positioner_implementation,
                    std::make_unique<WaylandPositioner>());
}

void xdg_wm_base_get_xdg_surface(wl_client* client,
                                 wl_resource* resource,
                                 uint32_t id,
                                 wl_resource* surface) {
  auto* data = GetUserDataAs<WaylandXdgShell>(resource);
  std::unique_ptr<XdgShellSurface> shell_surface =
      data->display->CreateXdgShellSurface(GetUserDataAs<Surface>(surface));
  if (!shell_surface) {
    wl_resource_post_error(resource, XDG_WM_BASE_ERROR_ROLE,
                           "surface has already been assigned a role");
    return;
  }

  // Xdg shell surfaces are initially disabled and needs to be explicitly mapped
  // before they are enabled and can become visible.
  shell_surface->SetEnabled(false);

  shell_surface->SetSecurityDelegate(GetSecurityDelegate(client));

  std::unique_ptr<WaylandXdgSurface> wayland_shell_surface =
      std::make_unique<WaylandXdgSurface>(std::move(shell_surface),
                                          data->serial_tracker,
                                          data->rotation_serial_tracker);

  wl_resource* xdg_surface_resource = wl_resource_create(
      client, &xdg_surface_interface, wl_resource_get_version(resource), id);

  SetImplementation(xdg_surface_resource, &xdg_surface_implementation,
                    std::move(wayland_shell_surface));
}

void xdg_wm_base_pong(wl_client* client,
                      wl_resource* resource,
                      uint32_t serial) {
  NOTIMPLEMENTED();
}

const struct xdg_wm_base_interface xdg_wm_base_implementation = {
    xdg_wm_base_destroy, xdg_wm_base_create_positioner,
    xdg_wm_base_get_xdg_surface, xdg_wm_base_pong};

////////////////////////////////////////////////////////////////////////////////
// Top level decoration
void toplevel_decoration_handle_destroy(wl_client* client,
                                        wl_resource* resource) {
  wl_resource_destroy(resource);
}

void toplevel_decoration_handle_set_mode(wl_client* client,
                                         wl_resource* resource,
                                         uint32_t mode) {
  GetUserDataAs<WaylandXdgToplevelDecoration>(resource)->SetDecorationMode(
      mode);
}

void toplevel_decoration_handle_unset_mode(wl_client* client,
                                           wl_resource* resource) {
  NOTIMPLEMENTED();
}

const struct zxdg_toplevel_decoration_v1_interface toplevel_decoration_impl = {
    .destroy = toplevel_decoration_handle_destroy,
    .set_mode = toplevel_decoration_handle_set_mode,
    .unset_mode = toplevel_decoration_handle_unset_mode,
};

// Decoration manager
void decoration_manager_handle_destroy(wl_client* client,
                                       wl_resource* manager_resource) {
  wl_resource_destroy(manager_resource);
}

void decoration_manager_handle_get_toplevel_decoration(
    wl_client* client,
    wl_resource* manager_resource,
    uint32_t id,
    wl_resource* toplevel_resource) {
  uint32_t version = wl_resource_get_version(manager_resource);
  wl_resource* decoration_resource = wl_resource_create(
      client, &zxdg_toplevel_decoration_v1_interface, version, id);
  if (!decoration_resource) {
    wl_client_post_no_memory(client);
    return;
  }

  auto xdg_toplevel_decoration = std::make_unique<WaylandXdgToplevelDecoration>(
      decoration_resource, toplevel_resource);

  SetImplementation(decoration_resource, &toplevel_decoration_impl,
                    std::move(xdg_toplevel_decoration));
}

static const struct zxdg_decoration_manager_v1_interface
    decoration_manager_impl = {
        .destroy = decoration_manager_handle_destroy,
        .get_toplevel_decoration =
            decoration_manager_handle_get_toplevel_decoration,
};

}  // namespace

WaylandXdgSurface ::WaylandXdgSurface(
    std::unique_ptr<XdgShellSurface> shell_surface,
    SerialTracker* const serial_tracker,
    SerialTracker* const rotation_serial_tracker)
    : shell_surface(std::move(shell_surface)),
      serial_tracker(serial_tracker),
      rotation_serial_tracker(rotation_serial_tracker) {}

WaylandXdgSurface::~WaylandXdgSurface() = default;

void bind_zxdg_decoration_manager(wl_client* client,
                                  void* data,
                                  uint32_t version,
                                  uint32_t id) {
  wl_resource* resource = wl_resource_create(
      client, &zxdg_decoration_manager_v1_interface, version, id);

  wl_resource_set_implementation(resource, &decoration_manager_impl, data,
                                 nullptr);
}

void bind_xdg_shell(wl_client* client,
                    void* data,
                    uint32_t version,
                    uint32_t id) {
  wl_resource* resource = wl_resource_create(client, &xdg_wm_base_interface,
                                             std::min(3u, version), id);

  wl_resource_set_implementation(resource, &xdg_wm_base_implementation, data,
                                 nullptr);
}

ShellSurfaceData GetShellSurfaceFromToplevelResource(wl_resource* resource) {
  auto* toplevel = GetUserDataAs<WaylandToplevel>(resource);
  return toplevel->GetShellSurfaceData();
}

ShellSurfaceBase* GetShellSurfaceFromPopupResource(wl_resource* resource) {
  auto* popup = GetUserDataAs<WaylandPopup>(resource);
  return popup->GetShellSurface();
}

}  // namespace wayland
}  // namespace exo