985 lines
35 KiB
C++
985 lines
35 KiB
C++
/*
|
|
* 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
|