blob: 39f0e26d1bb3cd486346a400c700a9d84e99bc2e [file] [log] [blame]
/*
* Copyright (C) 2016 Igalia S.L.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "HeadlessViewBackend.h"
#include <cassert>
#include <fcntl.h>
#include <mutex>
#include <unistd.h>
#if WPE_FDO_CHECK_VERSION(1,7,0)
#include <wayland-server.h>
#include <wpe/unstable/fdo-shm.h>
#endif
namespace WPEToolingBackends {
struct HeadlessInstance {
static const HeadlessInstance& singleton()
{
static std::once_flag s_onceFlag;
static HeadlessInstance s_instance;
std::call_once(s_onceFlag,
[] {
#if WPE_FDO_CHECK_VERSION(1,7,0)
wpe_fdo_initialize_shm();
#endif
});
return s_instance;
}
};
static cairo_user_data_key_t s_bufferKey;
HeadlessViewBackend::HeadlessViewBackend(uint32_t width, uint32_t height)
: ViewBackend(width, height)
{
// This should initialize the SHM mode.
HeadlessInstance::singleton();
static struct wpe_view_backend_exportable_fdo_client exportableClient = {
nullptr,
nullptr,
#if WPE_FDO_CHECK_VERSION(1,7,0)
// export_shm_buffer
[](void* data, struct wpe_fdo_shm_exported_buffer* buffer)
{
auto& backend = *static_cast<HeadlessViewBackend*>(data);
backend.m_update.pending = true;
backend.updateSnapshot(buffer);
wpe_view_backend_exportable_fdo_dispatch_release_shm_exported_buffer(backend.m_exportable, buffer);
},
#else
nullptr,
#endif
nullptr,
nullptr,
};
m_exportable = wpe_view_backend_exportable_fdo_create(&exportableClient, this, width, height);
addActivityState(wpe_view_activity_state_visible | wpe_view_activity_state_focused | wpe_view_activity_state_in_window);
{
uint32_t stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, m_width);
uint8_t* buffer = new uint8_t[stride * m_height];
memset(buffer, 0, stride * m_height);
m_snapshot = cairo_image_surface_create_for_data(buffer, CAIRO_FORMAT_ARGB32,
m_width, m_height, stride);
cairo_surface_set_user_data(m_snapshot, &s_bufferKey, buffer,
[](void* data) {
auto* buffer = static_cast<uint8_t*>(data);
delete[] buffer;
});
cairo_surface_mark_dirty(m_snapshot);
}
#if WPE_CHECK_VERSION(1, 11, 1)
wpe_view_backend_set_fullscreen_handler(backend(), onDOMFullScreenRequest, this);
#endif
m_update.source = g_timeout_source_new(G_USEC_PER_SEC / 60000);
g_source_set_callback(m_update.source, [](gpointer data) -> gboolean {
static_cast<HeadlessViewBackend*>(data)->vsync();
return TRUE;
}, this, nullptr);
g_source_set_priority(m_update.source, G_PRIORITY_DEFAULT);
g_source_attach(m_update.source, g_main_context_default());
}
HeadlessViewBackend::~HeadlessViewBackend()
{
if (m_update.source) {
g_source_destroy(m_update.source);
g_source_unref(m_update.source);
}
if (m_snapshot)
cairo_surface_destroy(m_snapshot);
if (m_exportable)
wpe_view_backend_exportable_fdo_destroy(m_exportable);
}
struct wpe_view_backend* HeadlessViewBackend::backend() const
{
if (m_exportable)
return wpe_view_backend_exportable_fdo_get_view_backend(m_exportable);
return nullptr;
}
cairo_surface_t* HeadlessViewBackend::snapshot()
{
return cairo_surface_reference(m_snapshot);
}
void HeadlessViewBackend::updateSnapshot(struct wpe_fdo_shm_exported_buffer* exportedBuffer)
{
#if WPE_FDO_CHECK_VERSION(1,7,0)
struct wl_shm_buffer* shmBuffer = wpe_fdo_shm_exported_buffer_get_shm_buffer(exportedBuffer);
{
auto format = wl_shm_buffer_get_format(shmBuffer);
if (format != WL_SHM_FORMAT_ARGB8888 && format != WL_SHM_FORMAT_XRGB8888)
return;
}
uint32_t bufferStride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, m_width);
uint8_t* buffer = new uint8_t[bufferStride * m_height];
memset(buffer, 0, bufferStride * m_height);
{
uint32_t width = std::min<uint32_t>(m_width, std::max(0, wl_shm_buffer_get_width(shmBuffer)));
uint32_t height = std::min<uint32_t>(m_height, std::max(0, wl_shm_buffer_get_height(shmBuffer)));
uint32_t stride = std::max(0, wl_shm_buffer_get_stride(shmBuffer));
wl_shm_buffer_begin_access(shmBuffer);
auto* data = static_cast<uint8_t*>(wl_shm_buffer_get_data(shmBuffer));
for (uint32_t y = 0; y < height; ++y) {
for (uint32_t x = 0; x < width; ++x) {
buffer[bufferStride * y + 4 * x + 0] = data[stride * y + 4 * x + 0];
buffer[bufferStride * y + 4 * x + 1] = data[stride * y + 4 * x + 1];
buffer[bufferStride * y + 4 * x + 2] = data[stride * y + 4 * x + 2];
buffer[bufferStride * y + 4 * x + 3] = data[stride * y + 4 * x + 3];
}
}
wl_shm_buffer_end_access(shmBuffer);
}
if (m_snapshot)
cairo_surface_destroy(m_snapshot);
m_snapshot = cairo_image_surface_create_for_data(buffer, CAIRO_FORMAT_ARGB32,
m_width, m_height, cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, m_width));
static cairo_user_data_key_t bufferKey;
cairo_surface_set_user_data(m_snapshot, &bufferKey, buffer,
[](void* data) {
auto* buffer = static_cast<uint8_t*>(data);
delete[] buffer;
});
cairo_surface_mark_dirty(m_snapshot);
#else
(void)exportedBuffer;
#endif
}
void HeadlessViewBackend::vsync()
{
#if WPE_FDO_CHECK_VERSION(1,7,0)
if (m_update.pending)
wpe_view_backend_exportable_fdo_dispatch_frame_complete(m_exportable);
#endif
m_update.pending = false;
}
#if WPE_CHECK_VERSION(1, 11, 1)
bool HeadlessViewBackend::onDOMFullScreenRequest(void* data, bool fullscreen)
{
auto& headless = *static_cast<HeadlessViewBackend*>(data);
if (fullscreen == headless.m_is_fullscreen) {
// Handle situations where DOM fullscreen requests are mixed with system fullscreen commands (e.g F11)
headless.dispatchFullscreenEvent();
return true;
}
headless.m_is_fullscreen = fullscreen;
return true;
}
void HeadlessViewBackend::dispatchFullscreenEvent()
{
if (m_is_fullscreen)
wpe_view_backend_dispatch_did_enter_fullscreen(backend());
else
wpe_view_backend_dispatch_did_exit_fullscreen(backend());
}
#endif
} // namespace WPEToolingBackends