| /* |
| * 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, 4)); |
| }, |
| // 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) |
| { |
| 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() }; |
| window.dispatchInputAxisEvent(&event); |
| } |
| }, |
| nullptr, // frame |
| nullptr, // axis_source |
| nullptr, // axis_stop |
| nullptr, // axis_discrete |
| }; |
| |
| 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*) { }, |
| }; |
| |
| 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; |
| // 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: |
| 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); |
| }, |
| // 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); |
| }, |
| }; |
| |
| 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_HIGH + 30); |
| 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); |
| |
| 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 |