| // Copyright 2013 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #define WL_EGL_PLATFORM |
| |
| #include "fl_renderer_wayland.h" |
| #include "flutter/shell/platform/linux/egl_utils.h" |
| |
| #include <gdk/gdkwayland.h> |
| #include <wayland-egl-core.h> |
| #include <cstring> |
| |
| struct _FlRendererWayland { |
| FlRenderer parent_instance; |
| wl_registry* registry; |
| wl_subcompositor* subcompositor; |
| |
| struct { |
| wl_subsurface* subsurface; |
| wl_surface* surface; |
| wl_egl_window* egl_window; |
| GdkRectangle geometry; |
| gint scale; |
| } subsurface; |
| |
| // The resource surface will not be mapped, but needs to be a wl_surface |
| // because ONLY window EGL surfaces are supported on Wayland. |
| struct { |
| wl_surface* surface; |
| wl_egl_window* egl_window; |
| } resource; |
| }; |
| |
| G_DEFINE_TYPE(FlRendererWayland, fl_renderer_wayland, fl_renderer_get_type()) |
| |
| // wl_registry.global callback. |
| static void registry_handle_global(void* data, |
| wl_registry* registry, |
| uint32_t id, |
| const char* name, |
| uint32_t max_version) { |
| FlRendererWayland* self = FL_RENDERER_WAYLAND(data); |
| if (strcmp(name, wl_subcompositor_interface.name) == 0) { |
| uint32_t version = MIN( |
| static_cast<uint32_t>(wl_subcompositor_interface.version), max_version); |
| self->subcompositor = static_cast<wl_subcompositor*>( |
| wl_registry_bind(registry, id, &wl_subcompositor_interface, version)); |
| } |
| } |
| |
| // wl_registry.global_remove callback. |
| // Can be safely ignored unless we bind to globals that might be removed. |
| static void registry_handle_global_remove(void*, wl_registry*, uint32_t) {} |
| |
| static const wl_registry_listener registry_listener = { |
| .global = registry_handle_global, |
| .global_remove = registry_handle_global_remove, |
| }; |
| |
| // The first time this function is called, all Wayland globals are initialized |
| // (which blocks for a round trip to the Wayland compositor). |
| // Subsequent calls return immediately. |
| static void fl_renderer_wayland_lazy_init_wl(FlRendererWayland* self) { |
| if (self->registry) { |
| return; |
| } |
| |
| GdkWaylandDisplay* gdk_display = |
| GDK_WAYLAND_DISPLAY(gdk_display_get_default()); |
| g_return_if_fail(gdk_display); |
| |
| wl_display* display = gdk_wayland_display_get_wl_display(gdk_display); |
| self->registry = wl_display_get_registry(display); |
| wl_registry_add_listener(self->registry, ®istry_listener, self); |
| wl_display_roundtrip(display); |
| } |
| |
| // Implements GObject::dispose. |
| static void fl_renderer_wayland_dispose(GObject* object) { |
| FlRendererWayland* self = FL_RENDERER_WAYLAND(object); |
| |
| g_clear_pointer(&self->registry, wl_registry_destroy); |
| g_clear_pointer(&self->subcompositor, wl_subcompositor_destroy); |
| |
| g_clear_pointer(&self->subsurface.subsurface, wl_subsurface_destroy); |
| g_clear_pointer(&self->subsurface.egl_window, wl_egl_window_destroy); |
| g_clear_pointer(&self->subsurface.surface, wl_surface_destroy); |
| |
| g_clear_pointer(&self->resource.egl_window, wl_egl_window_destroy); |
| g_clear_pointer(&self->resource.surface, wl_surface_destroy); |
| |
| G_OBJECT_CLASS(fl_renderer_wayland_parent_class)->dispose(object); |
| } |
| |
| // Implements FlRenderer::create_display. |
| static EGLDisplay fl_renderer_wayland_create_display(FlRenderer* /*renderer*/) { |
| GdkWaylandDisplay* gdk_display = |
| GDK_WAYLAND_DISPLAY(gdk_display_get_default()); |
| g_return_val_if_fail(gdk_display, nullptr); |
| return eglGetDisplay(gdk_wayland_display_get_wl_display(gdk_display)); |
| } |
| |
| static void fl_renderer_wayland_on_window_map(FlRendererWayland* self, |
| GtkWidget* widget) { |
| if (self->subsurface.subsurface) { |
| g_error("fl_renderer_wayland_on_window_map: already has a subsurface"); |
| return; |
| } |
| |
| GdkWaylandDisplay* gdk_display = |
| GDK_WAYLAND_DISPLAY(gdk_display_get_default()); |
| |
| wl_compositor* compositor = |
| gdk_wayland_display_get_wl_compositor(gdk_display); |
| |
| fl_renderer_wayland_lazy_init_wl(self); |
| if (!self->subcompositor) { |
| g_error( |
| "fl_renderer_wayland_on_window_map: could not bind to " |
| "wl_subcompositor"); |
| return; |
| } |
| |
| GdkWaylandWindow* window = GDK_WAYLAND_WINDOW(gtk_widget_get_window(widget)); |
| if (!window) { |
| g_error("fl_renderer_wayland_on_window_map: not a Wayland window"); |
| return; |
| } |
| wl_surface* toplevel_surface = gdk_wayland_window_get_wl_surface(window); |
| if (!toplevel_surface) { |
| g_error("fl_renderer_wayland_on_window_map: could not get wl_surface"); |
| return; |
| } |
| |
| self->subsurface.subsurface = wl_subcompositor_get_subsurface( |
| self->subcompositor, self->subsurface.surface, toplevel_surface); |
| if (!self->subsurface.subsurface) { |
| g_error("fl_renderer_wayland_on_window_map: could not create subsurface"); |
| return; |
| } |
| |
| wl_subsurface_set_desync(self->subsurface.subsurface); |
| wl_subsurface_set_position(self->subsurface.subsurface, |
| self->subsurface.geometry.x, |
| self->subsurface.geometry.y); |
| |
| // Give the subsurface an empty input region so the main surface gets input. |
| wl_region* region = wl_compositor_create_region(compositor); |
| wl_surface_set_input_region(self->subsurface.surface, region); |
| wl_region_destroy(region); |
| |
| wl_surface_commit(self->subsurface.surface); |
| } |
| |
| static void fl_renderer_wayland_on_window_unmap(FlRendererWayland* self, |
| GtkWidget* widget) { |
| g_clear_pointer(&self->subsurface.subsurface, wl_subsurface_destroy); |
| } |
| |
| // Implements FlRenderer::create_surfaces. |
| static gboolean fl_renderer_wayland_create_surfaces(FlRenderer* renderer, |
| GtkWidget* widget, |
| EGLDisplay display, |
| EGLConfig config, |
| EGLSurface* visible, |
| EGLSurface* resource, |
| GError** error) { |
| FlRendererWayland* self = FL_RENDERER_WAYLAND(renderer); |
| |
| if (self->subsurface.surface || self->subsurface.egl_window || |
| self->resource.surface || self->resource.egl_window) { |
| g_set_error(error, fl_renderer_error_quark(), FL_RENDERER_ERROR_FAILED, |
| "Surfaces already created"); |
| return FALSE; |
| } |
| |
| if (!GDK_IS_WAYLAND_DISPLAY(gdk_display_get_default())) { |
| g_set_error(error, fl_renderer_error_quark(), FL_RENDERER_ERROR_FAILED, |
| "Expected Wayland display"); |
| return FALSE; |
| } |
| GdkWaylandDisplay* gdk_display = |
| GDK_WAYLAND_DISPLAY(gdk_display_get_default()); |
| |
| wl_compositor* compositor = |
| gdk_wayland_display_get_wl_compositor(gdk_display); |
| if (!compositor) { |
| g_set_error(error, fl_renderer_error_quark(), FL_RENDERER_ERROR_FAILED, |
| "No wl_compositor"); |
| return FALSE; |
| } |
| |
| // Make sure size and scale is not <= 0 |
| self->subsurface.scale = MAX(self->subsurface.scale, 1); |
| self->subsurface.geometry.width = MAX(self->subsurface.geometry.width, 1); |
| self->subsurface.geometry.height = MAX(self->subsurface.geometry.height, 1); |
| |
| self->subsurface.surface = wl_compositor_create_surface(compositor); |
| self->resource.surface = wl_compositor_create_surface(compositor); |
| if (!self->subsurface.surface || !self->resource.surface) { |
| g_set_error(error, fl_renderer_error_quark(), FL_RENDERER_ERROR_FAILED, |
| "Failed to create wl_surfaces"); |
| return FALSE; |
| } |
| wl_surface_set_buffer_scale(self->subsurface.surface, self->subsurface.scale); |
| |
| gint window_width = self->subsurface.geometry.width * self->subsurface.scale; |
| gint window_height = |
| self->subsurface.geometry.height * self->subsurface.scale; |
| self->subsurface.egl_window = wl_egl_window_create( |
| self->subsurface.surface, window_width, window_height); |
| self->resource.egl_window = |
| wl_egl_window_create(self->resource.surface, 1, 1); |
| if (!self->subsurface.egl_window || !self->resource.egl_window) { |
| g_set_error(error, fl_renderer_error_quark(), FL_RENDERER_ERROR_FAILED, |
| "Failed to create EGL windows"); |
| return FALSE; |
| } |
| |
| *visible = eglCreateWindowSurface(display, config, |
| self->subsurface.egl_window, nullptr); |
| *resource = eglCreateWindowSurface(display, config, self->resource.egl_window, |
| nullptr); |
| if (*visible == EGL_NO_SURFACE || *resource == EGL_NO_SURFACE) { |
| EGLint egl_error = eglGetError(); // must be before egl_config_to_string() |
| g_autofree gchar* config_string = egl_config_to_string(display, config); |
| g_set_error(error, fl_renderer_error_quark(), FL_RENDERER_ERROR_FAILED, |
| "Failed to create EGL surfaces using configuration (%s): %s", |
| config_string, egl_error_to_string(egl_error)); |
| return FALSE; |
| } |
| |
| GtkWidget* toplevel = gtk_widget_get_toplevel(widget); |
| if (!toplevel) { |
| g_set_error(error, fl_renderer_error_quark(), FL_RENDERER_ERROR_FAILED, |
| "Renderer does not have a widget"); |
| return FALSE; |
| } |
| g_signal_connect_object(toplevel, "map", |
| G_CALLBACK(fl_renderer_wayland_on_window_map), self, |
| G_CONNECT_SWAPPED); |
| g_signal_connect_object(toplevel, "unmap", |
| G_CALLBACK(fl_renderer_wayland_on_window_unmap), self, |
| G_CONNECT_SWAPPED); |
| if (gtk_widget_get_mapped(toplevel)) { |
| fl_renderer_wayland_on_window_map(self, toplevel); |
| } |
| |
| return TRUE; |
| } |
| |
| // Implements FlRenderer::set_geometry. |
| static void fl_renderer_wayland_set_geometry(FlRenderer* renderer, |
| GdkRectangle* geometry, |
| gint scale) { |
| FlRendererWayland* self = FL_RENDERER_WAYLAND(renderer); |
| |
| if (scale != self->subsurface.scale && self->subsurface.surface) { |
| wl_surface_set_buffer_scale(self->subsurface.surface, scale); |
| } |
| |
| // NOTE: position is unscaled but size is scaled. |
| |
| if ((geometry->x != self->subsurface.geometry.x || |
| geometry->y != self->subsurface.geometry.y) && |
| self->subsurface.subsurface) { |
| wl_subsurface_set_position(self->subsurface.subsurface, geometry->x, |
| geometry->y); |
| } |
| |
| if ((geometry->width != self->subsurface.geometry.width || |
| geometry->height != self->subsurface.geometry.height || |
| scale != self->subsurface.scale) && |
| self->subsurface.egl_window) { |
| wl_egl_window_resize(self->subsurface.egl_window, geometry->width * scale, |
| geometry->height * scale, 0, 0); |
| } |
| |
| self->subsurface.geometry = *geometry; |
| self->subsurface.scale = scale; |
| } |
| |
| static void fl_renderer_wayland_class_init(FlRendererWaylandClass* klass) { |
| G_OBJECT_CLASS(klass)->dispose = fl_renderer_wayland_dispose; |
| FL_RENDERER_CLASS(klass)->create_display = fl_renderer_wayland_create_display; |
| FL_RENDERER_CLASS(klass)->create_surfaces = |
| fl_renderer_wayland_create_surfaces; |
| FL_RENDERER_CLASS(klass)->set_geometry = fl_renderer_wayland_set_geometry; |
| } |
| |
| static void fl_renderer_wayland_init(FlRendererWayland* self) {} |
| |
| FlRendererWayland* fl_renderer_wayland_new() { |
| return FL_RENDERER_WAYLAND( |
| g_object_new(fl_renderer_wayland_get_type(), nullptr)); |
| } |