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