blob: dfae1f93a8eeabeabcc3587be28788de41f68ddc [file] [log] [blame]
/*
* Copyright (C) 2018 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 "WindowViewBackend.h"
#include <cstdio>
#include <cstring>
#include <linux/input.h>
#include <memory>
#include <mutex>
#include <sys/mman.h>
#include <unistd.h>
// This include order is necessary to enforce the Wayland EGL platform.
#include <wayland-egl.h>
#include <epoxy/egl.h>
#include <wpe/fdo-egl.h>
#ifndef EGL_WL_bind_wayland_display
#define EGL_WL_bind_wayland_display 1
typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYWAYLANDBUFFERWL) (EGLDisplay dpy, struct wl_resource* buffer, EGLint attribute, EGLint* value);
#define EGL_WAYLAND_BUFFER_WL 0x31D5 // eglCreateImageKHR target
#define EGL_WAYLAND_PLANE_WL 0x31D6 // eglCreateImageKHR target
#endif
namespace WPEToolingBackends {
struct WaylandEGLConnection {
struct wl_display* display { nullptr };
EGLDisplay eglDisplay { EGL_NO_DISPLAY };
static const WaylandEGLConnection& singleton()
{
static std::once_flag s_onceFlag;
static WaylandEGLConnection s_connection;
std::call_once(s_onceFlag,
[] {
s_connection.display = wl_display_connect(nullptr);
if (!s_connection.display) {
g_warning("WaylandEGLConnection: Could not connect to Wayland Display");
return;
}
EGLDisplay eglDisplay = eglGetDisplay(s_connection.display);
if (eglDisplay == EGL_NO_DISPLAY) {
g_warning("WaylandEGLConnection: No EGL Display available in this connection");
return;
}
if (!eglInitialize(eglDisplay, nullptr, nullptr) || !eglBindAPI(EGL_OPENGL_ES_API)) {
g_warning("WaylandEGLConnection: Failed to initialize and bind the EGL Display");
return;
}
s_connection.eglDisplay = eglDisplay;
wpe_fdo_initialize_for_egl_display(s_connection.eglDisplay);
});
return s_connection;
}
};
static PFNGLEGLIMAGETARGETTEXTURE2DOESPROC imageTargetTexture2DOES;
struct EventSource {
static GSourceFuncs sourceFuncs;
GSource source;
GPollFD pfd;
struct wl_display* display;
};
GSourceFuncs EventSource::sourceFuncs = {
// prepare
[](GSource* base, gint* timeout) -> gboolean
{
auto* source = reinterpret_cast<EventSource*>(base);
struct wl_display* display = source->display;
*timeout = -1;
wl_display_dispatch_pending(display);
wl_display_flush(display);
return FALSE;
},
// check
[](GSource* base) -> gboolean
{
auto* source = reinterpret_cast<EventSource*>(base);
return !!source->pfd.revents;
},
// dispatch
[](GSource* base, GSourceFunc, gpointer) -> gboolean
{
auto* source = reinterpret_cast<EventSource*>(base);
struct wl_display* display = source->display;
if (source->pfd.revents & G_IO_IN)
wl_display_dispatch(display);
if (source->pfd.revents & (G_IO_ERR | G_IO_HUP))
return FALSE;
source->pfd.revents = 0;
return TRUE;
},
nullptr, // finalize
nullptr, // closure_callback
nullptr, // closure_marshall
};
const struct wl_registry_listener WindowViewBackend::s_registryListener = {
// global
[](void* data, struct wl_registry* registry, uint32_t name, const char* interface, uint32_t)
{
auto* window = static_cast<WindowViewBackend*>(data);
if (!std::strcmp(interface, "wl_compositor"))
window->m_compositor = static_cast<struct wl_compositor*>(wl_registry_bind(registry, name, &wl_compositor_interface, 1));
if (!std::strcmp(interface, "zxdg_shell_v6"))
window->m_xdg = static_cast<struct zxdg_shell_v6*>(wl_registry_bind(registry, name, &zxdg_shell_v6_interface, 1));
if (!std::strcmp(interface, "wl_seat"))
window->m_seat = static_cast<struct wl_seat*>(wl_registry_bind(registry, name, &wl_seat_interface, 5));
},
// global_remove
[](void*, struct wl_registry*, uint32_t) { },
};
const struct zxdg_shell_v6_listener WindowViewBackend::s_xdgWmBaseListener = {
// ping
[](void*, struct zxdg_shell_v6* shell, uint32_t serial)
{
zxdg_shell_v6_pong(shell, serial);
},
};
const struct wl_pointer_listener WindowViewBackend::s_pointerListener = {
// enter
[](void* data, struct wl_pointer*, uint32_t /*serial*/, struct wl_surface* surface, wl_fixed_t, wl_fixed_t)
{
auto& window = *static_cast<WindowViewBackend*>(data);
if (window.m_surface == surface) {
window.m_seatData.pointer.target = surface;
window.m_seatData.pointer.modifiers = 0;
}
},
// leave
[](void* data, struct wl_pointer*, uint32_t /*serial*/, struct wl_surface* surface)
{
auto& window = *static_cast<WindowViewBackend*>(data);
if (window.m_surface == surface && window.m_seatData.pointer.target == surface)
window.m_seatData.pointer.target = nullptr;
},
// motion
[](void* data, struct wl_pointer*, uint32_t time, wl_fixed_t fixedX, wl_fixed_t fixedY)
{
auto& window = *static_cast<WindowViewBackend*>(data);
int x = wl_fixed_to_int(fixedX);
int y = wl_fixed_to_int(fixedY);
window.m_seatData.pointer.coords = { x, y };
if (window.m_seatData.pointer.target) {
struct wpe_input_pointer_event event = { wpe_input_pointer_event_type_motion,
time, x, y, window.m_seatData.pointer.button, window.m_seatData.pointer.state, window.modifiers() };
window.dispatchInputPointerEvent(&event);
}
},
// button
[](void* data, struct wl_pointer*, uint32_t /*serial*/, uint32_t time, uint32_t button, uint32_t state)
{
auto& window = *static_cast<WindowViewBackend*>(data);
if (button >= BTN_MOUSE)
button = button - BTN_MOUSE + 1;
else
button = 0;
window.m_seatData.pointer.button = !!state ? button : 0;
window.m_seatData.pointer.state = state;
uint32_t modifier = 0;
switch (button) {
case 1:
modifier = wpe_input_pointer_modifier_button1;
break;
case 2:
modifier = wpe_input_pointer_modifier_button2;
break;
case 3:
modifier = wpe_input_pointer_modifier_button3;
break;
case 4:
modifier = wpe_input_pointer_modifier_button4;
break;
case 5:
modifier = wpe_input_pointer_modifier_button5;
break;
default:
break;
}
if (state)
window.m_seatData.pointer.modifiers |= modifier;
else
window.m_seatData.pointer.modifiers &= ~modifier;
if (window.m_seatData.pointer.target) {
struct wpe_input_pointer_event event = { wpe_input_pointer_event_type_button,
time, window.m_seatData.pointer.coords.first, window.m_seatData.pointer.coords.second, button, state, window.modifiers() };
window.dispatchInputPointerEvent(&event);
}
},
// axis
[](void* data, struct wl_pointer*, uint32_t time, uint32_t axis, wl_fixed_t value)
{
if (axis != WL_POINTER_AXIS_HORIZONTAL_SCROLL && axis != WL_POINTER_AXIS_VERTICAL_SCROLL)
return;
auto& window = *static_cast<WindowViewBackend*>(data);
if (window.m_seatData.pointer.target) {
struct wpe_input_axis_event event = { wpe_input_axis_event_type_motion,
time, window.m_seatData.pointer.coords.first, window.m_seatData.pointer.coords.second, axis, -wl_fixed_to_int(value), window.modifiers() };
if (axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL && window.m_seatData.axis_discrete.horizontal)
event.value = window.m_seatData.axis_discrete.horizontal;
else if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL && window.m_seatData.axis_discrete.vertical)
event.value = window.m_seatData.axis_discrete.vertical;
#if WPE_CHECK_VERSION(1, 5, 0)
else {
struct wpe_input_axis_2d_event event2d = { event, 0, 0 };
event2d.base.type = static_cast<wpe_input_axis_event_type>(wpe_input_axis_event_type_mask_2d | wpe_input_axis_event_type_motion_smooth);
if (axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL)
event2d.x_axis = wl_fixed_to_double(value);
else
event2d.y_axis = -wl_fixed_to_double(value);
window.dispatchInputAxisEvent(&event2d.base);
return;
}
#endif
window.dispatchInputAxisEvent(&event);
window.m_seatData.axis_discrete.horizontal = window.m_seatData.axis_discrete.vertical = 0;
}
},
// frame
[](void*, struct wl_pointer*) { },
// axis_source
[](void*, struct wl_pointer*, uint32_t) { },
// axis_stop
[](void* data, struct wl_pointer*, uint32_t time, uint32_t axis)
{
if (axis != WL_POINTER_AXIS_HORIZONTAL_SCROLL && axis != WL_POINTER_AXIS_VERTICAL_SCROLL)
return;
auto& window = *static_cast<WindowViewBackend*>(data);
if (window.m_seatData.pointer.target) {
struct wpe_input_axis_event event = { wpe_input_axis_event_type_motion,
time, window.m_seatData.pointer.coords.first, window.m_seatData.pointer.coords.second, axis, 0, window.modifiers() };
window.dispatchInputAxisEvent(&event);
}
},
// axis_discrete
[](void* data, struct wl_pointer*, uint32_t axis, int32_t discrete) {
auto& window = *static_cast<WindowViewBackend*>(data);
switch (axis) {
case WL_POINTER_AXIS_HORIZONTAL_SCROLL:
window.m_seatData.axis_discrete.horizontal = discrete;
break;
case WL_POINTER_AXIS_VERTICAL_SCROLL:
window.m_seatData.axis_discrete.vertical = -discrete;
break;
}
},
};
const struct wl_keyboard_listener WindowViewBackend::s_keyboardListener = {
// keymap
[](void*, struct wl_keyboard*, uint32_t format, int fd, uint32_t size)
{
if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) {
close(fd);
return;
}
void* mapping = mmap(nullptr, size, PROT_READ, MAP_SHARED, fd, 0);
if (mapping == MAP_FAILED) {
close(fd);
return;
}
auto* xkb = wpe_input_xkb_context_get_default();
auto* keymap = xkb_keymap_new_from_string(wpe_input_xkb_context_get_context(xkb), static_cast<char*>(mapping),
XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS);
munmap(mapping, size);
close(fd);
wpe_input_xkb_context_set_keymap(xkb, keymap);
xkb_keymap_unref(keymap);
},
// enter
[](void* data, struct wl_keyboard*, uint32_t /*serial*/, struct wl_surface* surface, struct wl_array*)
{
auto& window = *static_cast<WindowViewBackend*>(data);
if (window.m_surface == surface)
window.m_seatData.keyboard.target = surface;
},
// leave
[](void* data, struct wl_keyboard*, uint32_t /*serial*/, struct wl_surface* surface)
{
auto& window = *static_cast<WindowViewBackend*>(data);
if (window.m_surface == surface && window.m_seatData.keyboard.target == surface)
window.m_seatData.keyboard.target = nullptr;
},
// key
[](void* data, struct wl_keyboard*, uint32_t /*serial*/, uint32_t time, uint32_t key, uint32_t state)
{
auto& window = *static_cast<WindowViewBackend*>(data);
// IDK.
key += 8;
window.handleKeyEvent(key, state, time);
auto& seatData = window.m_seatData;
if (!seatData.repeatInfo.rate)
return;
auto* keymap = wpe_input_xkb_context_get_keymap(wpe_input_xkb_context_get_default());
if (state == WL_KEYBOARD_KEY_STATE_RELEASED
&& seatData.repeatData.key == key) {
if (seatData.repeatData.eventSource)
g_source_remove(seatData.repeatData.eventSource);
seatData.repeatData = { 0, 0, 0, 0 };
} else if (state == WL_KEYBOARD_KEY_STATE_PRESSED
&& keymap && xkb_keymap_key_repeats(keymap, key)) {
if (seatData.repeatData.eventSource)
g_source_remove(seatData.repeatData.eventSource);
auto sourceID = g_timeout_add(seatData.repeatInfo.delay, [](void* data) -> gboolean {
auto& window = *static_cast<WindowViewBackend*>(data);
auto& seatData = window.m_seatData;
window.handleKeyEvent(seatData.repeatData.key, seatData.repeatData.state, seatData.repeatData.time);
seatData.repeatData.eventSource = g_timeout_add(seatData.repeatInfo.rate, [](void* data) -> gboolean {
auto& window = *static_cast<WindowViewBackend*>(data);
auto& seatData = window.m_seatData;
window.handleKeyEvent(seatData.repeatData.key, seatData.repeatData.state, seatData.repeatData.time);
return G_SOURCE_CONTINUE;
}, data);
return G_SOURCE_REMOVE;
}, data);
seatData.repeatData = { key, time, state, sourceID };
}
},
// modifiers
[](void* data, struct wl_keyboard*, uint32_t /*serial*/, uint32_t depressedMods, uint32_t latchedMods, uint32_t lockedMods, uint32_t group)
{
auto& keyboard = static_cast<WindowViewBackend*>(data)->m_seatData.keyboard;
keyboard.modifiers = wpe_input_xkb_context_get_modifiers(wpe_input_xkb_context_get_default(), depressedMods, latchedMods, lockedMods, group);
},
// repeat_info
[](void* data, struct wl_keyboard*, int32_t rate, int32_t delay)
{
auto& seatData = static_cast<WindowViewBackend*>(data)->m_seatData;
auto& repeatInfo = seatData.repeatInfo;
repeatInfo = { rate, delay };
// A rate of zero disables any repeating.
if (!rate) {
auto& repeatData = seatData.repeatData;
if (repeatData.eventSource) {
g_source_remove(repeatData.eventSource);
repeatData = { 0, 0, 0, 0 };
}
}
},
};
const struct wl_touch_listener WindowViewBackend::s_touchListener = {
// down
[](void* data, struct wl_touch*, uint32_t, uint32_t time, struct wl_surface* surface, int32_t id, wl_fixed_t x, wl_fixed_t y)
{
auto& window = *static_cast<WindowViewBackend*>(data);
if (window.m_surface != surface || id < 0 || id >= 10)
return;
auto& seatData = window.m_seatData;
seatData.touch.tracking = true;
struct wpe_input_touch_event_raw rawEvent = { wpe_input_touch_event_type_down,
time, id, wl_fixed_to_int(x), wl_fixed_to_int(y) };
memcpy(&seatData.touch.points[id], &rawEvent, sizeof(struct wpe_input_touch_event_raw));
struct wpe_input_touch_event event = { seatData.touch.points, 10,
rawEvent.type, rawEvent.id, rawEvent.time, window.modifiers() };
window.dispatchInputTouchEvent(&event);
},
// up
[](void* data, struct wl_touch*, uint32_t, uint32_t time, int32_t id)
{
auto& window = *static_cast<WindowViewBackend*>(data);
auto& seatData = window.m_seatData;
if (!seatData.touch.tracking || id < 0 || id >= 10)
return;
seatData.touch.tracking = false;
struct wpe_input_touch_event_raw rawEvent = { wpe_input_touch_event_type_up,
time, id, seatData.touch.points[id].x, seatData.touch.points[id].y };
memcpy(&seatData.touch.points[id], &rawEvent, sizeof(struct wpe_input_touch_event_raw));
struct wpe_input_touch_event event = { seatData.touch.points, 10,
rawEvent.type, rawEvent.id, rawEvent.time, window.modifiers() };
window.dispatchInputTouchEvent(&event);
memset(&seatData.touch.points[id], 0x00, sizeof(struct wpe_input_touch_event_raw));
},
// motion
[](void* data, struct wl_touch*, uint32_t time, int32_t id, wl_fixed_t x, wl_fixed_t y)
{
auto& window = *static_cast<WindowViewBackend*>(data);
auto& seatData = window.m_seatData;
if (!seatData.touch.tracking || id < 0 || id >= 10)
return;
struct wpe_input_touch_event_raw rawEvent = { wpe_input_touch_event_type_motion,
time, id, wl_fixed_to_int(x), wl_fixed_to_int(y) };
memcpy(&seatData.touch.points[id], &rawEvent, sizeof(struct wpe_input_touch_event_raw));
struct wpe_input_touch_event event = { seatData.touch.points, 10,
rawEvent.type, rawEvent.id, rawEvent.time, window.modifiers() };
window.dispatchInputTouchEvent(&event);
},
// frame
[](void*, struct wl_touch*) { },
// cancel
[](void*, struct wl_touch*) { },
// shape
[](void*, struct wl_touch*, int32_t, wl_fixed_t, wl_fixed_t) { },
// orientation
[](void*, struct wl_touch*, int32_t, wl_fixed_t) { },
};
const struct wl_seat_listener WindowViewBackend::s_seatListener = {
// capabilities
[](void* data, struct wl_seat* seat, uint32_t capabilities)
{
auto* window = static_cast<WindowViewBackend*>(data);
auto& seatData = window->m_seatData;
// WL_SEAT_CAPABILITY_POINTER
const bool hasPointerCap = capabilities & WL_SEAT_CAPABILITY_POINTER;
if (hasPointerCap && !seatData.pointer.object) {
seatData.pointer.object = wl_seat_get_pointer(seat);
wl_pointer_add_listener(seatData.pointer.object, &s_pointerListener, window);
}
if (!hasPointerCap && seatData.pointer.object) {
wl_pointer_destroy(seatData.pointer.object);
seatData.pointer.object = nullptr;
}
// WL_SEAT_CAPABILITY_KEYBOARD
const bool hasKeyboardCap = capabilities & WL_SEAT_CAPABILITY_KEYBOARD;
if (hasKeyboardCap && !seatData.keyboard.object) {
seatData.keyboard.object = wl_seat_get_keyboard(seat);
wl_keyboard_add_listener(seatData.keyboard.object, &s_keyboardListener, window);
}
if (!hasKeyboardCap && seatData.keyboard.object) {
wl_keyboard_destroy(seatData.keyboard.object);
seatData.keyboard.object = nullptr;
}
// WL_SEAT_CAPABILITY_TOUCH
const bool hasTouchCap = capabilities & WL_SEAT_CAPABILITY_TOUCH;
if (hasTouchCap && !seatData.touch.object) {
seatData.touch.object = wl_seat_get_touch(seat);
wl_touch_add_listener(seatData.touch.object, &s_touchListener, window);
}
if (!hasTouchCap && seatData.touch.object) {
wl_touch_destroy(seatData.touch.object);
seatData.touch.object = nullptr;
}
},
// name
[](void*, struct wl_seat*, const char*) { }
};
const struct zxdg_surface_v6_listener WindowViewBackend::s_xdgSurfaceListener = {
// configure
[](void*, struct zxdg_surface_v6* surface, uint32_t serial)
{
zxdg_surface_v6_ack_configure(surface, serial);
},
};
const struct zxdg_toplevel_v6_listener WindowViewBackend::s_xdgToplevelListener = {
// configure
[](void* data, struct zxdg_toplevel_v6*, int32_t width, int32_t height, struct wl_array* states)
{
auto& window = *static_cast<WindowViewBackend*>(data);
window.resize(std::max(0, width), std::max(0, height));
bool isFocused = false;
bool isFullscreen = false;
// FIXME: It would be nice if the following loop could use
// wl_array_for_each, but at the time of writing it relies on
// GCC specific extension to work properly:
// https://gitlab.freedesktop.org/wayland/wayland/issues/34
uint32_t* pos = static_cast<uint32_t*>(states->data);
uint32_t* end = static_cast<uint32_t*>(states->data) + states->size;
for (; pos < end; pos++) {
uint32_t state = *pos;
switch (state) {
case ZXDG_TOPLEVEL_V6_STATE_ACTIVATED:
isFocused = true;
break;
case ZXDG_TOPLEVEL_V6_STATE_FULLSCREEN:
isFullscreen = true;
break;
case ZXDG_TOPLEVEL_V6_STATE_MAXIMIZED:
case ZXDG_TOPLEVEL_V6_STATE_RESIZING:
default:
break;
}
}
if (isFocused)
window.addActivityState(wpe_view_activity_state_focused);
else
window.removeActivityState(wpe_view_activity_state_focused);
if (window.m_is_fullscreen != isFullscreen)
window.onFullscreenChanged(isFullscreen);
},
// close
[](void* data, struct zxdg_toplevel_v6*)
{
auto& window = *static_cast<WindowViewBackend*>(data);
window.removeActivityState(wpe_view_activity_state_visible | wpe_view_activity_state_focused | wpe_view_activity_state_in_window);
},
};
#if WPE_CHECK_VERSION(1, 11, 1)
bool WindowViewBackend::onDOMFullscreenRequest(void* data, bool fullscreen)
{
auto& window = *static_cast<WindowViewBackend*>(data);
if (window.m_waiting_fullscreen_notify)
return false;
if (fullscreen == window.m_is_fullscreen) {
// Handle situations where DOM fullscreen requests are mixed with system fullscreen commands (e.g F11)
window.dispatchFullscreenEvent();
return true;
}
window.m_waiting_fullscreen_notify = true;
if (fullscreen)
zxdg_toplevel_v6_set_fullscreen(window.m_xdgToplevel, nullptr);
else
zxdg_toplevel_v6_unset_fullscreen(window.m_xdgToplevel);
return true;
}
void WindowViewBackend::dispatchFullscreenEvent()
{
if (m_is_fullscreen)
wpe_view_backend_dispatch_did_enter_fullscreen(backend());
else
wpe_view_backend_dispatch_did_exit_fullscreen(backend());
}
void WindowViewBackend::onFullscreenChanged(bool fullscreen)
{
bool wasRequestedFromDOM = m_waiting_fullscreen_notify;
m_waiting_fullscreen_notify= false;
m_is_fullscreen = fullscreen;
if (!fullscreen && !wasRequestedFromDOM)
wpe_view_backend_dispatch_request_exit_fullscreen(backend());
else if (wasRequestedFromDOM)
dispatchFullscreenEvent();
}
#else
void WindowViewBackend::onFullscreenChanged(bool fullscreen)
{
m_is_fullscreen = fullscreen;
}
#endif // WPE_CHECK_VERSION(1, 11, 1)
WindowViewBackend::WindowViewBackend(uint32_t width, uint32_t height)
: ViewBackend(width, height)
{
m_initialSize.width = width;
m_initialSize.height = height;
auto& connection = WaylandEGLConnection::singleton();
if (!connection.display) {
g_warning("WindowViewBackend: No Wayland EGL connection available");
return;
}
if (connection.eglDisplay == EGL_NO_DISPLAY || !initialize(connection.eglDisplay)) {
g_warning("WindowViewBackend: Could not initialize EGL display");
return;
}
{
auto* registry = wl_display_get_registry(connection.display);
wl_registry_add_listener(registry, &s_registryListener, this);
wl_display_roundtrip(connection.display);
if (m_xdg)
zxdg_shell_v6_add_listener(m_xdg, &s_xdgWmBaseListener, nullptr);
if (m_seat)
wl_seat_add_listener(m_seat, &s_seatListener, this);
}
m_eventSource = g_source_new(&EventSource::sourceFuncs, sizeof(EventSource));
{
auto& source = *reinterpret_cast<EventSource*>(m_eventSource);
source.display = connection.display;
source.pfd.fd = wl_display_get_fd(connection.display);
source.pfd.events = G_IO_IN | G_IO_ERR | G_IO_HUP;
source.pfd.revents = 0;
g_source_add_poll(&source.source, &source.pfd);
g_source_set_priority(&source.source, G_PRIORITY_DEFAULT);
g_source_set_can_recurse(&source.source, TRUE);
g_source_attach(&source.source, g_main_context_get_thread_default());
}
m_surface = wl_compositor_create_surface(m_compositor);
if (m_xdg) {
m_xdgSurface = zxdg_shell_v6_get_xdg_surface(m_xdg, m_surface);
zxdg_surface_v6_add_listener(m_xdgSurface, &s_xdgSurfaceListener, nullptr);
m_xdgToplevel = zxdg_surface_v6_get_toplevel(m_xdgSurface);
if (m_xdgToplevel) {
zxdg_toplevel_v6_add_listener(m_xdgToplevel, &s_xdgToplevelListener, this);
zxdg_toplevel_v6_set_title(m_xdgToplevel, "WPE");
wl_surface_commit(m_surface);
addActivityState(wpe_view_activity_state_visible | wpe_view_activity_state_in_window);
}
}
m_eglWindow = wl_egl_window_create(m_surface, m_width, m_height);
auto createPlatformWindowSurface =
reinterpret_cast<PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC>(eglGetProcAddress("eglCreatePlatformWindowSurfaceEXT"));
m_eglSurface = createPlatformWindowSurface(connection.eglDisplay, m_eglConfig, m_eglWindow, nullptr);
if (!m_eglSurface) {
g_warning("WindowViewBackend: Could not create EGL platform window surface");
return;
}
if (!eglMakeCurrent(connection.eglDisplay, m_eglSurface, m_eglSurface, m_eglContext)) {
g_warning("WindowViewBackend: Could not make EGL surface current");
return;
}
imageTargetTexture2DOES = reinterpret_cast<PFNGLEGLIMAGETARGETTEXTURE2DOESPROC>(eglGetProcAddress("glEGLImageTargetTexture2DOES"));
{
static const char* vertexShaderSource =
"attribute vec2 pos;\n"
"attribute vec2 texture;\n"
"varying vec2 v_texture;\n"
"void main() {\n"
" v_texture = texture;\n"
" gl_Position = vec4(pos, 0, 1);\n"
"}\n";
static const char* fragmentShaderSource =
"precision mediump float;\n"
"uniform sampler2D u_texture;\n"
"varying vec2 v_texture;\n"
"void main() {\n"
" gl_FragColor = texture2D(u_texture, v_texture);\n"
"}\n";
GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSource, nullptr);
glCompileShader(vertexShader);
GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, nullptr);
glCompileShader(fragmentShader);
m_program = glCreateProgram();
glAttachShader(m_program, vertexShader);
glAttachShader(m_program, fragmentShader);
glLinkProgram(m_program);
glBindAttribLocation(m_program, 0, "pos");
glBindAttribLocation(m_program, 1, "texture");
m_textureUniform = glGetUniformLocation(m_program, "u_texture");
}
createViewTexture();
}
WindowViewBackend::~WindowViewBackend()
{
auto& connection = WaylandEGLConnection::singleton();
if (m_eventSource) {
g_source_destroy(m_eventSource);
g_source_unref(m_eventSource);
}
if (m_xdgToplevel)
zxdg_toplevel_v6_destroy(m_xdgToplevel);
if (m_xdgSurface)
zxdg_surface_v6_destroy(m_xdgSurface);
if (m_surface)
wl_surface_destroy(m_surface);
if (m_eglWindow)
wl_egl_window_destroy(m_eglWindow);
if (m_xdg)
zxdg_shell_v6_destroy(m_xdg);
if (m_seat)
wl_seat_destroy(m_seat);
if (m_compositor)
wl_compositor_destroy(m_compositor);
if (m_eglSurface)
eglDestroySurface(connection.eglDisplay, m_eglSurface);
if (m_display)
wl_display_disconnect(m_display);
deinitialize(connection.eglDisplay);
}
const struct wl_callback_listener WindowViewBackend::s_frameListener = {
// frame
[](void* data, struct wl_callback* callback, uint32_t)
{
if (callback)
wl_callback_destroy(callback);
auto& window = *static_cast<WindowViewBackend*>(data);
wpe_view_backend_exportable_fdo_dispatch_frame_complete(window.m_exportable);
if (window.m_committedImage)
wpe_view_backend_exportable_fdo_egl_dispatch_release_exported_image(window.m_exportable, window.m_committedImage);
window.m_committedImage = nullptr;
}
};
struct wpe_view_backend* WindowViewBackend::backend() const
{
return m_exportable ? wpe_view_backend_exportable_fdo_get_view_backend(m_exportable) : nullptr;
}
void WindowViewBackend::createViewTexture()
{
glGenTextures(1, &m_viewTexture);
glBindTexture(GL_TEXTURE_2D, m_viewTexture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_width, m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
glBindTexture(GL_TEXTURE_2D, 0);
}
void WindowViewBackend::resize(uint32_t width, uint32_t height)
{
if (!width)
width = m_initialSize.width;
if (!height)
height = m_initialSize.height;
if (width == m_width && height == m_height)
return;
m_width = width;
m_height = height;
wl_egl_window_resize(m_eglWindow, m_width, m_height, 0, 0);
wpe_view_backend_dispatch_set_size(backend(), m_width, m_height);
if (m_viewTexture)
glDeleteTextures(1, &m_viewTexture);
createViewTexture();
}
bool WindowViewBackend::initialize(EGLDisplay eglDisplay)
{
static const EGLint configAttributes[13] = {
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
EGL_RED_SIZE, 1,
EGL_GREEN_SIZE, 1,
EGL_BLUE_SIZE, 1,
EGL_ALPHA_SIZE, 1,
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
EGL_NONE
};
{
EGLint count = 0;
if (!eglGetConfigs(eglDisplay, nullptr, 0, &count) || count < 1)
return false;
EGLConfig* configs = g_new0(EGLConfig, count);
EGLint matched = 0;
if (eglChooseConfig(eglDisplay, configAttributes, configs, count, &matched) && !!matched)
m_eglConfig = configs[0];
g_free(configs);
}
static const EGLint contextAttributes[3] = {
EGL_CONTEXT_CLIENT_VERSION, 2,
EGL_NONE
};
m_eglContext = eglCreateContext(eglDisplay, m_eglConfig, EGL_NO_CONTEXT, contextAttributes);
if (!m_eglContext)
return false;
static struct wpe_view_backend_exportable_fdo_egl_client exportableClient = {
// export_egl_image
nullptr,
// export_fdo_egl_image
[](void* data, struct wpe_fdo_egl_exported_image* image)
{
static_cast<WindowViewBackend*>(data)->displayBuffer(image);
},
#if WPE_FDO_CHECK_VERSION(1, 5, 0)
// export_shm_buffer
[](void* data, struct wpe_fdo_shm_exported_buffer* buffer)
{
static_cast<WindowViewBackend*>(data)->displayBuffer(buffer);
},
// padding
nullptr, nullptr
#else
// padding
nullptr, nullptr, nullptr
#endif
};
m_exportable = wpe_view_backend_exportable_fdo_egl_create(&exportableClient, this, m_width, m_height);
#if WPE_CHECK_VERSION(1, 11, 1)
wpe_view_backend_set_fullscreen_handler(backend(), onDOMFullscreenRequest, this);
#endif
initializeAccessibility();
return true;
}
void WindowViewBackend::deinitialize(EGLDisplay eglDisplay)
{
m_inputClient = nullptr;
if (m_eglContext)
eglDestroyContext(eglDisplay, m_eglContext);
if (m_exportable)
wpe_view_backend_exportable_fdo_destroy(m_exportable);
}
void WindowViewBackend::displayBuffer(struct wpe_fdo_egl_exported_image* image)
{
if (!m_eglContext)
return;
auto& connection = WaylandEGLConnection::singleton();
eglMakeCurrent(connection.eglDisplay, m_eglSurface, m_eglSurface, m_eglContext);
glViewport(0, 0, m_width, m_height);
glClearColor(1, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(m_program);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, m_viewTexture);
imageTargetTexture2DOES(GL_TEXTURE_2D, wpe_fdo_egl_exported_image_get_egl_image(image));
glUniform1i(m_textureUniform, 0);
m_committedImage = image;
static const GLfloat vertices[4][2] = {
{ -1.0, 1.0 },
{ 1.0, 1.0 },
{ -1.0, -1.0 },
{ 1.0, -1.0 },
};
static const GLfloat texturePos[4][2] = {
{ 0, 0 },
{ 1, 0 },
{ 0, 1 },
{ 1, 1 },
};
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, vertices);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, texturePos);
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glDisableVertexAttribArray(0);
glDisableVertexAttribArray(1);
struct wl_callback* callback = wl_surface_frame(m_surface);
wl_callback_add_listener(callback, &s_frameListener, this);
eglSwapBuffers(connection.eglDisplay, m_eglSurface);
}
#if WPE_FDO_CHECK_VERSION(1, 5, 0)
void WindowViewBackend::displayBuffer(struct wpe_fdo_shm_exported_buffer*)
{
g_warning("WindowViewBackend: cannot yet handle wpe_fdo_shm_exported_buffer.");
}
#endif
void WindowViewBackend::handleKeyEvent(uint32_t key, uint32_t state, uint32_t time)
{
uint32_t keysym = wpe_input_xkb_context_get_key_code(wpe_input_xkb_context_get_default(), key, state == WL_KEYBOARD_KEY_STATE_PRESSED);
if (!keysym)
return;
if (m_seatData.keyboard.target) {
struct wpe_input_keyboard_event event = { time, keysym, key, !!state, modifiers() };
dispatchInputKeyboardEvent(&event);
}
}
uint32_t WindowViewBackend::modifiers() const
{
uint32_t mask = m_seatData.keyboard.modifiers;
if (m_seatData.pointer.object)
mask |= m_seatData.pointer.modifiers;
return mask;
}
} // namespace WPEToolingBackends