diff --git a/staging/mutter/3567.patch b/staging/mutter/0002-3567.patch similarity index 100% rename from staging/mutter/3567.patch rename to staging/mutter/0002-3567.patch diff --git a/staging/mutter/0003-3304.patch b/staging/mutter/0003-3304.patch new file mode 100644 index 0000000..745e6b1 --- /dev/null +++ b/staging/mutter/0003-3304.patch @@ -0,0 +1,854 @@ +diff --git a/src/backends/meta-egl.c b/src/backends/meta-egl.c +index 9a57c52de..2f8247b3a 100644 +--- a/src/backends/meta-egl.c ++++ b/src/backends/meta-egl.c +@@ -260,6 +260,14 @@ meta_egl_has_extensions (MetaEgl *egl, + return has_extensions; + } + ++const char * ++meta_egl_query_string (MetaEgl *egl, ++ EGLDisplay display, ++ EGLint name) ++{ ++ return eglQueryString (display, name); ++} ++ + gboolean + meta_egl_initialize (MetaEgl *egl, + EGLDisplay display, +diff --git a/src/backends/meta-egl.h b/src/backends/meta-egl.h +index 09bee954a..8b955c90c 100644 +--- a/src/backends/meta-egl.h ++++ b/src/backends/meta-egl.h +@@ -47,6 +47,10 @@ gboolean meta_egl_has_extensions (MetaEgl *egl, + const char *first_extension, + ...); + ++const char * meta_egl_query_string (MetaEgl *egl, ++ EGLDisplay display, ++ EGLint name); ++ + gboolean meta_egl_initialize (MetaEgl *egl, + EGLDisplay display, + GError **error); +diff --git a/src/backends/native/meta-onscreen-native.c b/src/backends/native/meta-onscreen-native.c +index 405ce80a4..086e217cb 100644 +--- a/src/backends/native/meta-onscreen-native.c ++++ b/src/backends/native/meta-onscreen-native.c +@@ -841,6 +841,9 @@ copy_shared_framebuffer_gpu (CoglOnscreen *onscreen, + COGL_TRACE_BEGIN_SCOPED (CopySharedFramebufferSecondaryGpu, + "copy_shared_framebuffer_gpu()"); + ++ if (renderer_gpu_data->secondary.needs_explicit_sync) ++ cogl_framebuffer_finish (COGL_FRAMEBUFFER (onscreen)); ++ + render_device = renderer_gpu_data->render_device; + egl_display = meta_render_device_get_egl_display (render_device); + +@@ -948,8 +951,7 @@ copy_shared_framebuffer_primary_gpu (CoglOnscreen *onscre + COGL_TRACE_BEGIN_SCOPED (CopySharedFramebufferPrimaryGpu, + "copy_shared_framebuffer_primary_gpu()"); + +- if (!secondary_gpu_state || +- secondary_gpu_state->egl_surface == EGL_NO_SURFACE) ++ if (!secondary_gpu_state) + return NULL; + + primary_gpu = meta_renderer_native_get_primary_gpu (renderer_native); +@@ -2418,6 +2420,15 @@ init_secondary_gpu_state_gpu_copy_mode (MetaRendererNative *renderer_nat + width, height, + format, + GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING); ++ ++ if (!gbm_surface) ++ { ++ gbm_surface = gbm_surface_create (gbm_device, ++ width, height, ++ format, ++ 0); ++ } ++ + if (!gbm_surface) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, +diff --git a/src/backends/native/meta-renderer-native-gles3.c b/src/backends/native/meta-renderer-native-gles3.c +index cf27ba8d4..0d50de177 100644 +--- a/src/backends/native/meta-renderer-native-gles3.c ++++ b/src/backends/native/meta-renderer-native-gles3.c +@@ -3,6 +3,7 @@ + /* + * Copyright (C) 2017 Red Hat + * Copyright (c) 2018 DisplayLink (UK) Ltd. ++ * Copyright (c) 2023 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as +@@ -34,6 +35,7 @@ + #include "backends/meta-egl-ext.h" + #include "backends/meta-gles3.h" + #include "backends/meta-gles3-table.h" ++#include "meta/meta-debug.h" + + /* + * GL/gl.h being included may conflict with gl3.h on some architectures. +@@ -43,17 +45,240 @@ + #error "Somehow included OpenGL headers when we shouldn't have" + #endif + ++typedef struct _ContextData ++{ ++ GArray *buffer_support; ++ GLuint shader_program; ++} ContextData; ++ ++typedef struct ++{ ++ uint32_t drm_format; ++ uint64_t drm_modifier; ++ gboolean can_blit; ++} BufferTypeSupport; ++ + static void +-paint_egl_image (MetaGles3 *gles3, +- EGLImageKHR egl_image, +- int width, +- int height) ++context_data_free (ContextData *context_data) ++{ ++ g_array_free (context_data->buffer_support, TRUE); ++ g_free (context_data); ++} ++ ++static GQuark ++get_quark_for_egl_context (EGLContext egl_context) ++{ ++ char key[128]; ++ ++ g_snprintf (key, sizeof key, "EGLContext %p", egl_context); ++ ++ return g_quark_from_string (key); ++} ++ ++static gboolean ++can_blit_buffer (ContextData *context_data, ++ MetaEgl *egl, ++ EGLDisplay egl_display, ++ uint32_t drm_format, ++ uint64_t drm_modifier) ++{ ++ EGLint num_modifiers; ++ EGLuint64KHR *modifiers; ++ EGLBoolean *external_only; ++ g_autoptr (GError) error = NULL; ++ int i; ++ gboolean can_blit; ++ BufferTypeSupport support; ++ ++ can_blit = drm_modifier == DRM_FORMAT_MOD_LINEAR; ++ ++ for (i = 0; i < context_data->buffer_support->len; i++) ++ { ++ BufferTypeSupport *support = ++ &g_array_index (context_data->buffer_support, BufferTypeSupport, i); ++ ++ if (support->drm_format == drm_format && ++ support->drm_modifier == drm_modifier) ++ return support->can_blit; ++ } ++ ++ if (!meta_egl_has_extensions (egl, egl_display, NULL, ++ "EGL_EXT_image_dma_buf_import_modifiers", ++ NULL)) ++ { ++ meta_topic (META_DEBUG_RENDER, ++ "No support for EGL_EXT_image_dma_buf_import_modifiers, " ++ "assuming blitting linearly will still work."); ++ goto out; ++ } ++ ++ if (!meta_egl_query_dma_buf_modifiers (egl, egl_display, ++ drm_format, 0, NULL, NULL, ++ &num_modifiers, &error)) ++ { ++ meta_topic (META_DEBUG_RENDER, ++ "Failed to query supported DMA buffer modifiers (%s), " ++ "assuming blitting linearly will still work.", ++ error->message); ++ goto out; ++ } ++ ++ if (num_modifiers == 0) ++ goto out; ++ ++ modifiers = g_alloca0 (sizeof (EGLuint64KHR) * num_modifiers); ++ external_only = g_alloca0 (sizeof (EGLBoolean) * num_modifiers); ++ if (!meta_egl_query_dma_buf_modifiers (egl, egl_display, ++ drm_format, num_modifiers, ++ modifiers, external_only, ++ &num_modifiers, &error)) ++ { ++ g_warning ("Failed to requery supported DMA buffer modifiers: %s", ++ error->message); ++ can_blit = FALSE; ++ goto out; ++ } ++ ++ can_blit = FALSE; ++ for (i = 0; i < num_modifiers; i++) ++ { ++ if (drm_modifier == modifiers[i]) ++ { ++ can_blit = !external_only[i]; ++ goto out; ++ } ++ } ++ ++out: ++ support = (BufferTypeSupport) { ++ .drm_format = drm_format, ++ .drm_modifier = drm_modifier, ++ .can_blit = can_blit, ++ }; ++ g_array_append_val (context_data->buffer_support, support); ++ return can_blit; ++} ++ ++static GLuint ++load_shader (const char *src, ++ GLenum type) ++{ ++ GLuint shader = glCreateShader (type); ++ ++ if (shader) ++ { ++ GLint compiled; ++ ++ glShaderSource (shader, 1, &src, NULL); ++ glCompileShader (shader); ++ glGetShaderiv (shader, GL_COMPILE_STATUS, &compiled); ++ if (!compiled) ++ { ++ GLchar log[1024]; ++ ++ glGetShaderInfoLog (shader, sizeof (log) - 1, NULL, log); ++ log[sizeof (log) - 1] = '\0'; ++ g_warning ("load_shader compile failed: %s", log); ++ glDeleteShader (shader); ++ shader = 0; ++ } ++ } ++ ++ return shader; ++} ++ ++static void ++ensure_shader_program (ContextData *context_data, ++ MetaGles3 *gles3) ++{ ++ static const char vertex_shader_source[] = ++ "#version 100\n" ++ "attribute vec2 position;\n" ++ "attribute vec2 texcoord;\n" ++ "varying vec2 v_texcoord;\n" ++ "\n" ++ "void main()\n" ++ "{\n" ++ " gl_Position = vec4(position, 0.0, 1.0);\n" ++ " v_texcoord = texcoord;\n" ++ "}\n"; ++ ++ static const char fragment_shader_source[] = ++ "#version 100\n" ++ "#extension GL_OES_EGL_image_external : require\n" ++ "precision mediump float;\n" ++ "uniform samplerExternalOES s_texture;\n" ++ "varying vec2 v_texcoord;\n" ++ "\n" ++ " void main()\n" ++ "{\n" ++ " gl_FragColor = texture2D(s_texture, v_texcoord);\n" ++ "}\n"; ++ ++ static const GLfloat box[] = ++ { /* position texcoord */ ++ -1.0f, +1.0f, 0.0f, 0.0f, ++ +1.0f, +1.0f, 1.0f, 0.0f, ++ +1.0f, -1.0f, 1.0f, 1.0f, ++ -1.0f, -1.0f, 0.0f, 1.0f, ++ }; ++ GLint linked; ++ GLuint vertex_shader, fragment_shader; ++ GLint position_attrib, texcoord_attrib; ++ GLuint shader_program; ++ ++ if (context_data->shader_program) ++ return; ++ ++ shader_program = glCreateProgram (); ++ g_return_if_fail (shader_program); ++ context_data->shader_program = shader_program; ++ ++ vertex_shader = load_shader (vertex_shader_source, GL_VERTEX_SHADER); ++ g_return_if_fail (vertex_shader); ++ fragment_shader = load_shader (fragment_shader_source, GL_FRAGMENT_SHADER); ++ g_return_if_fail (fragment_shader); ++ ++ GLBAS (gles3, glAttachShader, (shader_program, vertex_shader)); ++ GLBAS (gles3, glAttachShader, (shader_program, fragment_shader)); ++ GLBAS (gles3, glLinkProgram, (shader_program)); ++ GLBAS (gles3, glGetProgramiv, (shader_program, GL_LINK_STATUS, &linked)); ++ if (!linked) ++ { ++ GLchar log[1024]; ++ ++ glGetProgramInfoLog (shader_program, sizeof (log) - 1, NULL, log); ++ log[sizeof (log) - 1] = '\0'; ++ g_warning ("Link failed: %s", log); ++ return; ++ } ++ ++ GLBAS (gles3, glUseProgram, (shader_program)); ++ ++ position_attrib = glGetAttribLocation (shader_program, "position"); ++ GLBAS (gles3, glEnableVertexAttribArray, (position_attrib)); ++ GLBAS (gles3, glVertexAttribPointer, ++ (position_attrib, 2, GL_FLOAT, GL_FALSE, 4 * sizeof (GLfloat), box)); ++ ++ texcoord_attrib = glGetAttribLocation (shader_program, "texcoord"); ++ GLBAS (gles3, glEnableVertexAttribArray, (texcoord_attrib)); ++ GLBAS (gles3, glVertexAttribPointer, ++ (texcoord_attrib, 2, GL_FLOAT, GL_FALSE, 4 * sizeof (GLfloat), box + 2)); ++} ++ ++static void ++blit_egl_image (MetaGles3 *gles3, ++ EGLImageKHR egl_image, ++ int width, ++ int height) + { + GLuint texture; + GLuint framebuffer; + + meta_gles3_clear_error (gles3); + ++ GLBAS (gles3, glViewport, (0, 0, width, height)); ++ + GLBAS (gles3, glGenFramebuffers, (1, &framebuffer)); + GLBAS (gles3, glBindFramebuffer, (GL_READ_FRAMEBUFFER, framebuffer)); + +@@ -85,6 +310,43 @@ paint_egl_image (MetaGles3 *gles3, + GLBAS (gles3, glDeleteFramebuffers, (1, &framebuffer)); + } + ++static void ++paint_egl_image (ContextData *context_data, ++ MetaGles3 *gles3, ++ EGLImageKHR egl_image, ++ int width, ++ int height) ++{ ++ GLuint texture; ++ ++ meta_gles3_clear_error (gles3); ++ ensure_shader_program (context_data, gles3); ++ ++ GLBAS (gles3, glViewport, (0, 0, width, height)); ++ ++ GLBAS (gles3, glActiveTexture, (GL_TEXTURE0)); ++ GLBAS (gles3, glGenTextures, (1, &texture)); ++ GLBAS (gles3, glBindTexture, (GL_TEXTURE_EXTERNAL_OES, texture)); ++ GLEXT (gles3, glEGLImageTargetTexture2DOES, (GL_TEXTURE_EXTERNAL_OES, ++ egl_image)); ++ GLBAS (gles3, glTexParameteri, (GL_TEXTURE_EXTERNAL_OES, ++ GL_TEXTURE_MAG_FILTER, ++ GL_NEAREST)); ++ GLBAS (gles3, glTexParameteri, (GL_TEXTURE_EXTERNAL_OES, ++ GL_TEXTURE_MIN_FILTER, ++ GL_NEAREST)); ++ GLBAS (gles3, glTexParameteri, (GL_TEXTURE_EXTERNAL_OES, ++ GL_TEXTURE_WRAP_S, ++ GL_CLAMP_TO_EDGE)); ++ GLBAS (gles3, glTexParameteri, (GL_TEXTURE_EXTERNAL_OES, ++ GL_TEXTURE_WRAP_T, ++ GL_CLAMP_TO_EDGE)); ++ ++ GLBAS (gles3, glDrawArrays, (GL_TRIANGLE_FAN, 0, 4)); ++ ++ GLBAS (gles3, glDeleteTextures, (1, &texture)); ++} ++ + gboolean + meta_renderer_native_gles3_blit_shared_bo (MetaEgl *egl, + MetaGles3 *gles3, +@@ -105,6 +367,28 @@ meta_renderer_native_gles3_blit_shared_bo (MetaEgl *egl, + uint32_t format; + EGLImageKHR egl_image; + gboolean use_modifiers; ++ GQuark context_data_quark; ++ ContextData *context_data; ++ gboolean can_blit; ++ ++ context_data_quark = get_quark_for_egl_context (egl_context); ++ context_data = g_object_get_qdata (G_OBJECT (gles3), context_data_quark); ++ if (!context_data) ++ { ++ context_data = g_new0 (ContextData, 1); ++ context_data->buffer_support = g_array_new (FALSE, FALSE, ++ sizeof (BufferTypeSupport)); ++ ++ g_object_set_qdata_full (G_OBJECT (gles3), ++ context_data_quark, ++ context_data, ++ (GDestroyNotify) context_data_free); ++ } ++ ++ can_blit = can_blit_buffer (context_data, ++ egl, egl_display, ++ gbm_bo_get_format (shared_bo), ++ gbm_bo_get_modifier (shared_bo)); + + shared_bo_fd = gbm_bo_get_fd (shared_bo); + if (shared_bo_fd < 0) +@@ -150,9 +434,21 @@ meta_renderer_native_gles3_blit_shared_bo (MetaEgl *egl, + if (!egl_image) + return FALSE; + +- paint_egl_image (gles3, egl_image, width, height); ++ if (can_blit) ++ blit_egl_image (gles3, egl_image, width, height); ++ else ++ paint_egl_image (context_data, gles3, egl_image, width, height); + + meta_egl_destroy_image (egl, egl_display, egl_image, NULL); + + return TRUE; + } ++ ++void ++meta_renderer_native_gles3_forget_context (MetaGles3 *gles3, ++ EGLContext egl_context) ++{ ++ GQuark context_data_quark = get_quark_for_egl_context (egl_context); ++ ++ g_object_set_qdata (G_OBJECT (gles3), context_data_quark, NULL); ++} +diff --git a/src/backends/native/meta-renderer-native-gles3.h b/src/backends/native/meta-renderer-native-gles3.h +index 591ff82e1..f5791a171 100644 +--- a/src/backends/native/meta-renderer-native-gles3.h ++++ b/src/backends/native/meta-renderer-native-gles3.h +@@ -26,10 +26,13 @@ + #include "backends/meta-egl.h" + #include "backends/meta-gles3.h" + +-gboolean meta_renderer_native_gles3_blit_shared_bo (MetaEgl *egl, +- MetaGles3 *gles3, +- EGLDisplay egl_display, +- EGLContext egl_context, +- EGLSurface egl_surface, +- struct gbm_bo *shared_bo, +- GError **error); ++gboolean meta_renderer_native_gles3_blit_shared_bo (MetaEgl *egl, ++ MetaGles3 *gles3, ++ EGLDisplay egl_display, ++ EGLContext egl_context, ++ EGLSurface egl_surface, ++ struct gbm_bo *shared_bo, ++ GError **error); ++ ++void meta_renderer_native_gles3_forget_context (MetaGles3 *gles3, ++ EGLContext egl_context); +diff --git a/src/backends/native/meta-renderer-native-private.h b/src/backends/native/meta-renderer-native-private.h +index 997fe6f69..2d3fe1971 100644 +--- a/src/backends/native/meta-renderer-native-private.h ++++ b/src/backends/native/meta-renderer-native-private.h +@@ -60,6 +60,7 @@ typedef struct _MetaRendererNativeGpuData + struct { + MetaSharedFramebufferCopyMode copy_mode; + gboolean has_EGL_EXT_image_dma_buf_import_modifiers; ++ gboolean needs_explicit_sync; + + /* For GPU blit mode */ + EGLContext egl_context; +diff --git a/src/backends/native/meta-renderer-native.c b/src/backends/native/meta-renderer-native.c +index 5ffbafb58..aa76d018c 100644 +--- a/src/backends/native/meta-renderer-native.c ++++ b/src/backends/native/meta-renderer-native.c +@@ -63,6 +63,7 @@ + #include "backends/native/meta-output-kms.h" + #include "backends/native/meta-render-device-gbm.h" + #include "backends/native/meta-render-device-surfaceless.h" ++#include "backends/native/meta-renderer-native-gles3.h" + #include "backends/native/meta-renderer-native-private.h" + #include "backends/native/meta-renderer-view-native.h" + #include "cogl/cogl.h" +@@ -137,6 +138,9 @@ meta_renderer_native_gpu_data_free (MetaRendererNativeGpuData *renderer_gpu_data + MetaRendererNative *renderer_native = renderer_gpu_data->renderer_native; + MetaEgl *egl = meta_renderer_native_get_egl (renderer_native); + ++ meta_renderer_native_gles3_forget_context (renderer_native->gles3, ++ renderer_gpu_data->secondary.egl_context); ++ + meta_egl_destroy_context (egl, + egl_display, + renderer_gpu_data->secondary.egl_context, +@@ -1806,6 +1810,7 @@ init_secondary_gpu_data_gpu (MetaRendererNativeGpuData *renderer_gpu_data, + CoglContext *cogl_context; + CoglDisplay *cogl_display; + const char **missing_gl_extensions; ++ const char *egl_vendor; + + egl_display = meta_render_device_get_egl_display (render_device); + if (egl_display == EGL_NO_DISPLAY) +@@ -1872,6 +1877,11 @@ init_secondary_gpu_data_gpu (MetaRendererNativeGpuData *renderer_gpu_data, + meta_egl_has_extensions (egl, egl_display, NULL, + "EGL_EXT_image_dma_buf_import_modifiers", + NULL); ++ ++ egl_vendor = meta_egl_query_string (egl, egl_display, EGL_VENDOR); ++ if (!g_strcmp0 (egl_vendor, "NVIDIA")) ++ renderer_gpu_data->secondary.needs_explicit_sync = TRUE; ++ + ret = TRUE; + out: + maybe_restore_cogl_egl_api (renderer_native); +diff --git a/src/meta/common.h b/src/meta/common.h +index ee978ac6a..ed3aa81ef 100644 +--- a/src/meta/common.h ++++ b/src/meta/common.h +@@ -27,6 +27,7 @@ + #include + + #include "clutter/clutter.h" ++#include "meta/meta-base.h" + #include "meta/meta-enums.h" + + /** +@@ -42,8 +43,6 @@ + /* Replacement for X11 CurrentTime */ + #define META_CURRENT_TIME 0L + +-#define META_EXPORT __attribute__((visibility("default"))) extern +- + #define MAX_BUTTONS_PER_CORNER META_BUTTON_FUNCTION_LAST + + /* Keep array size in sync with MAX_BUTTONS_PER_CORNER */ +diff --git a/src/meta/meson.build b/src/meta/meson.build +index 655dedeaf..bc3910408 100644 +--- a/src/meta/meson.build ++++ b/src/meta/meson.build +@@ -9,6 +9,7 @@ mutter_public_headers = [ + 'keybindings.h', + 'main.h', + 'meta-backend.h', ++ 'meta-base.h', + 'meta-background.h', + 'meta-background-actor.h', + 'meta-background-content.h', +@@ -17,6 +18,7 @@ mutter_public_headers = [ + 'meta-close-dialog.h', + 'meta-cursor-tracker.h', + 'meta-context.h', ++ 'meta-debug.h', + 'meta-dnd.h', + 'meta-enums.h', + 'meta-idle-monitor.h', +diff --git a/src/meta/meta-base.h b/src/meta/meta-base.h +new file mode 100644 +index 000000000..e344b2221 +--- /dev/null ++++ b/src/meta/meta-base.h +@@ -0,0 +1,21 @@ ++/* ++ * Copyright (C) 2001 Havoc Pennington ++ * Copyright (C) 2005 Elijah Newren ++ * ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License as ++ * published by the Free Software Foundation; either version 2 of the ++ * License, or (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, but ++ * WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, see . ++ */ ++ ++#pragma once ++ ++#define META_EXPORT __attribute__((visibility("default"))) extern +diff --git a/src/meta/meta-debug.h b/src/meta/meta-debug.h +new file mode 100644 +index 000000000..b4c70144b +--- /dev/null ++++ b/src/meta/meta-debug.h +@@ -0,0 +1,127 @@ ++/* ++ * Copyright (C) 2001 Havoc Pennington ++ * Copyright (C) 2005 Elijah Newren ++ * ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License as ++ * published by the Free Software Foundation; either version 2 of the ++ * License, or (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, but ++ * WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, see . ++ */ ++ ++#pragma once ++ ++#include "meta/meta-base.h" ++ ++/** ++ * MetaDebugTopic: ++ * @META_DEBUG_VERBOSE: verbose logging ++ * @META_DEBUG_FOCUS: focus ++ * @META_DEBUG_WORKAREA: workarea ++ * @META_DEBUG_STACK: stack ++ * @META_DEBUG_SM: session management ++ * @META_DEBUG_EVENTS: events ++ * @META_DEBUG_WINDOW_STATE: window state ++ * @META_DEBUG_WINDOW_OPS: window operations ++ * @META_DEBUG_GEOMETRY: geometry ++ * @META_DEBUG_PLACEMENT: window placement ++ * @META_DEBUG_PING: ping ++ * @META_DEBUG_KEYBINDINGS: keybindings ++ * @META_DEBUG_SYNC: sync ++ * @META_DEBUG_STARTUP: startup ++ * @META_DEBUG_PREFS: preferences ++ * @META_DEBUG_GROUPS: groups ++ * @META_DEBUG_RESIZING: resizing ++ * @META_DEBUG_SHAPES: shapes ++ * @META_DEBUG_EDGE_RESISTANCE: edge resistance ++ * @META_DEBUG_WAYLAND: Wayland ++ * @META_DEBUG_KMS: kernel mode setting ++ * @META_DEBUG_SCREEN_CAST: screencasting ++ * @META_DEBUG_REMOTE_DESKTOP: remote desktop ++ * @META_DEBUG_BACKEND: backend ++ * @META_DEBUG_RENDER: native backend rendering ++ * @META_DEBUG_COLOR: color management ++ * @META_DEBUG_INPUT_EVENTS: input events ++ * @META_DEBUG_EIS: eis state ++ */ ++typedef enum ++{ ++ META_DEBUG_VERBOSE = -1, ++ META_DEBUG_FOCUS = 1 << 0, ++ META_DEBUG_WORKAREA = 1 << 1, ++ META_DEBUG_STACK = 1 << 2, ++ META_DEBUG_SM = 1 << 3, ++ META_DEBUG_EVENTS = 1 << 4, ++ META_DEBUG_WINDOW_STATE = 1 << 5, ++ META_DEBUG_WINDOW_OPS = 1 << 6, ++ META_DEBUG_GEOMETRY = 1 << 7, ++ META_DEBUG_PLACEMENT = 1 << 8, ++ META_DEBUG_PING = 1 << 9, ++ META_DEBUG_KEYBINDINGS = 1 << 10, ++ META_DEBUG_SYNC = 1 << 11, ++ META_DEBUG_STARTUP = 1 << 12, ++ META_DEBUG_PREFS = 1 << 13, ++ META_DEBUG_GROUPS = 1 << 14, ++ META_DEBUG_RESIZING = 1 << 15, ++ META_DEBUG_SHAPES = 1 << 16, ++ META_DEBUG_EDGE_RESISTANCE = 1 << 17, ++ META_DEBUG_DBUS = 1 << 18, ++ META_DEBUG_INPUT = 1 << 19, ++ META_DEBUG_WAYLAND = 1 << 20, ++ META_DEBUG_KMS = 1 << 21, ++ META_DEBUG_SCREEN_CAST = 1 << 22, ++ META_DEBUG_REMOTE_DESKTOP = 1 << 23, ++ META_DEBUG_BACKEND = 1 << 24, ++ META_DEBUG_RENDER = 1 << 25, ++ META_DEBUG_COLOR = 1 << 26, ++ META_DEBUG_INPUT_EVENTS = 1 << 27, ++ META_DEBUG_EIS = 1 << 28, ++} MetaDebugTopic; ++ ++META_EXPORT ++gboolean meta_is_topic_enabled (MetaDebugTopic topic); ++ ++/* To disable verbose mode, we make these functions into no-ops */ ++#ifdef WITH_VERBOSE_MODE ++ ++const char * meta_topic_to_string (MetaDebugTopic topic); ++ ++META_EXPORT ++void meta_log (const char *format, ...) G_GNUC_PRINTF (1, 2); ++ ++#define meta_topic(debug_topic, ...) \ ++ G_STMT_START \ ++ { \ ++ if (meta_is_topic_enabled (debug_topic)) \ ++ { \ ++ g_autofree char *_topic_message = NULL; \ ++\ ++ _topic_message = g_strdup_printf (__VA_ARGS__); \ ++ meta_log ("%s: %s", meta_topic_to_string (debug_topic), \ ++ _topic_message); \ ++ } \ ++ } \ ++ G_STMT_END ++ ++#define meta_verbose(...) meta_topic (META_DEBUG_VERBOSE, __VA_ARGS__) ++ ++#else ++ ++# ifdef G_HAVE_ISO_VARARGS ++# define meta_verbose(...) ++# define meta_topic(...) ++# elif defined(G_HAVE_GNUC_VARARGS) ++# define meta_verbose(format...) ++# define meta_topic(format...) ++# else ++# error "This compiler does not support varargs macros and thus verbose mode can't be disabled meaningfully" ++# endif ++ ++#endif /* !WITH_VERBOSE_MODE */ +diff --git a/src/meta/util.h b/src/meta/util.h +index 0019c8078..ca8ca2ade 100644 +--- a/src/meta/util.h ++++ b/src/meta/util.h +@@ -26,6 +26,7 @@ + #include + + #include "meta/common.h" ++#include "meta/meta-debug.h" + #include "meta/meta-later.h" + + META_EXPORT +@@ -46,71 +47,6 @@ META_EXPORT + void meta_fatal (const char *format, + ...) G_GNUC_PRINTF (1, 2) G_GNUC_NORETURN G_ANALYZER_NORETURN; + +-/** +- * MetaDebugTopic: +- * @META_DEBUG_VERBOSE: verbose logging +- * @META_DEBUG_FOCUS: focus +- * @META_DEBUG_WORKAREA: workarea +- * @META_DEBUG_STACK: stack +- * @META_DEBUG_SM: session management +- * @META_DEBUG_EVENTS: events +- * @META_DEBUG_WINDOW_STATE: window state +- * @META_DEBUG_WINDOW_OPS: window operations +- * @META_DEBUG_GEOMETRY: geometry +- * @META_DEBUG_PLACEMENT: window placement +- * @META_DEBUG_PING: ping +- * @META_DEBUG_KEYBINDINGS: keybindings +- * @META_DEBUG_SYNC: sync +- * @META_DEBUG_STARTUP: startup +- * @META_DEBUG_PREFS: preferences +- * @META_DEBUG_GROUPS: groups +- * @META_DEBUG_RESIZING: resizing +- * @META_DEBUG_SHAPES: shapes +- * @META_DEBUG_EDGE_RESISTANCE: edge resistance +- * @META_DEBUG_WAYLAND: Wayland +- * @META_DEBUG_KMS: kernel mode setting +- * @META_DEBUG_SCREEN_CAST: screencasting +- * @META_DEBUG_REMOTE_DESKTOP: remote desktop +- * @META_DEBUG_BACKEND: backend +- * @META_DEBUG_RENDER: native backend rendering +- * @META_DEBUG_COLOR: color management +- * @META_DEBUG_INPUT_EVENTS: input events +- * @META_DEBUG_EIS: eis state +- */ +-typedef enum +-{ +- META_DEBUG_VERBOSE = -1, +- META_DEBUG_FOCUS = 1 << 0, +- META_DEBUG_WORKAREA = 1 << 1, +- META_DEBUG_STACK = 1 << 2, +- META_DEBUG_SM = 1 << 3, +- META_DEBUG_EVENTS = 1 << 4, +- META_DEBUG_WINDOW_STATE = 1 << 5, +- META_DEBUG_WINDOW_OPS = 1 << 6, +- META_DEBUG_GEOMETRY = 1 << 7, +- META_DEBUG_PLACEMENT = 1 << 8, +- META_DEBUG_PING = 1 << 9, +- META_DEBUG_KEYBINDINGS = 1 << 10, +- META_DEBUG_SYNC = 1 << 11, +- META_DEBUG_STARTUP = 1 << 12, +- META_DEBUG_PREFS = 1 << 13, +- META_DEBUG_GROUPS = 1 << 14, +- META_DEBUG_RESIZING = 1 << 15, +- META_DEBUG_SHAPES = 1 << 16, +- META_DEBUG_EDGE_RESISTANCE = 1 << 17, +- META_DEBUG_DBUS = 1 << 18, +- META_DEBUG_INPUT = 1 << 19, +- META_DEBUG_WAYLAND = 1 << 20, +- META_DEBUG_KMS = 1 << 21, +- META_DEBUG_SCREEN_CAST = 1 << 22, +- META_DEBUG_REMOTE_DESKTOP = 1 << 23, +- META_DEBUG_BACKEND = 1 << 24, +- META_DEBUG_RENDER = 1 << 25, +- META_DEBUG_COLOR = 1 << 26, +- META_DEBUG_INPUT_EVENTS = 1 << 27, +- META_DEBUG_EIS = 1 << 28, +-} MetaDebugTopic; +- + /** + * MetaDebugPaintFlag: + * @META_DEBUG_PAINT_NONE: default +@@ -122,9 +58,6 @@ typedef enum + META_DEBUG_PAINT_OPAQUE_REGION = 1 << 0, + } MetaDebugPaintFlag; + +-META_EXPORT +-gboolean meta_is_topic_enabled (MetaDebugTopic topic); +- + META_EXPORT + void meta_add_verbose_topic (MetaDebugTopic topic); + +@@ -155,44 +88,6 @@ char* meta_external_binding_name_for_action (guint keybinding_action); + META_EXPORT + char* meta_g_utf8_strndup (const gchar *src, gsize n); + +-/* To disable verbose mode, we make these functions into no-ops */ +-#ifdef WITH_VERBOSE_MODE +- +-const char * meta_topic_to_string (MetaDebugTopic topic); +- +-META_EXPORT +-void meta_log (const char *format, ...) G_GNUC_PRINTF (1, 2); +- +-#define meta_topic(debug_topic, ...) \ +- G_STMT_START \ +- { \ +- if (meta_is_topic_enabled (debug_topic)) \ +- { \ +- g_autofree char *_topic_message = NULL; \ +-\ +- _topic_message = g_strdup_printf (__VA_ARGS__); \ +- meta_log ("%s: %s", meta_topic_to_string (debug_topic), \ +- _topic_message); \ +- } \ +- } \ +- G_STMT_END +- +-#define meta_verbose(...) meta_topic (META_DEBUG_VERBOSE, __VA_ARGS__) +- +-#else +- +-# ifdef G_HAVE_ISO_VARARGS +-# define meta_verbose(...) +-# define meta_topic(...) +-# elif defined(G_HAVE_GNUC_VARARGS) +-# define meta_verbose(format...) +-# define meta_topic(format...) +-# else +-# error "This compiler does not support varargs macros and thus verbose mode can't be disabled meaningfully" +-# endif +- +-#endif /* !WITH_VERBOSE_MODE */ +- + typedef enum + { + META_LOCALE_DIRECTION_LTR, diff --git a/staging/mutter/0004-1441.patch b/staging/mutter/0004-1441.patch new file mode 100644 index 0000000..ce61d92 --- /dev/null +++ b/staging/mutter/0004-1441.patch @@ -0,0 +1,8396 @@ +diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml +index 7aadfceb2..5020c9bed 100644 +--- a/.gitlab-ci.yml ++++ b/.gitlab-ci.yml +@@ -237,6 +237,29 @@ workflow: + - if: $CI_MERGE_REQUEST_TARGET_BRANCH_NAME + when: on_success + ++repo-sanity: ++ extends: ++ - .fdo.ci-fairy ++ stage: review ++ variables: ++ GIT_DEPTH: "1" ++ script: ++ - > ++ if [[ -z "$CI_REGISTRY_IMAGE" ]] ; ++ then ++ .gitlab-ci/simple-junit-report.sh check-junit-report.xml \ ++ repo-sanity "The container registry should be enabled in the project general settings panel at $CI_PROJECT_URL/edit" ; ++ exit 1 ; ++ fi ++ artifacts: ++ expire_in: 1 week ++ paths: ++ - check-junit-report.xml ++ reports: ++ junit: check-junit-report.xml ++ rules: ++ - !reference [.only-merge-requests, rules] ++ + check-commit-log: + extends: + - .fdo.ci-fairy +diff --git a/.gitlab-ci/simple-junit-report.sh b/.gitlab-ci/simple-junit-report.sh +new file mode 100755 +index 000000000..3a60324d3 +--- /dev/null ++++ b/.gitlab-ci/simple-junit-report.sh +@@ -0,0 +1,18 @@ ++#!/bin/bash ++OUTFILE=$1 ++NAME=$2 ++MESSAGE=$3 ++ ++cat >$OUTFILE < ++ ++ ++ ++ ++ ++ ++ ++EOF ++ ++# Also echo the message in stdout for good measure ++echo $MESSAGE +diff --git a/clutter/clutter/clutter-frame-clock.c b/clutter/clutter/clutter-frame-clock.c +index 93e4c9329..a6c7ecea2 100644 +--- a/clutter/clutter/clutter-frame-clock.c ++++ b/clutter/clutter/clutter-frame-clock.c +@@ -42,6 +42,15 @@ enum + + static guint signals[N_SIGNALS]; + ++typedef enum ++{ ++ TRIPLE_BUFFERING_MODE_NEVER, ++ TRIPLE_BUFFERING_MODE_AUTO, ++ TRIPLE_BUFFERING_MODE_ALWAYS, ++} TripleBufferingMode; ++ ++static TripleBufferingMode triple_buffering_mode = TRIPLE_BUFFERING_MODE_AUTO; ++ + #define SYNC_DELAY_FALLBACK_FRACTION 0.875 + + #define MINIMUM_REFRESH_RATE 30.f +@@ -70,8 +79,10 @@ typedef enum _ClutterFrameClockState + CLUTTER_FRAME_CLOCK_STATE_IDLE, + CLUTTER_FRAME_CLOCK_STATE_SCHEDULED, + CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW, +- CLUTTER_FRAME_CLOCK_STATE_DISPATCHING, +- CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED, ++ CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE, ++ CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED, ++ CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW, ++ CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO, + } ClutterFrameClockState; + + struct _ClutterFrameClock +@@ -92,6 +103,7 @@ struct _ClutterFrameClock + ClutterFrameClockMode mode; + + int64_t last_dispatch_time_us; ++ int64_t prev_last_dispatch_time_us; + int64_t last_dispatch_lateness_us; + int64_t last_presentation_time_us; + int64_t next_update_time_us; +@@ -111,6 +123,9 @@ struct _ClutterFrameClock + int64_t vblank_duration_us; + /* Last KMS buffer submission time. */ + int64_t last_flip_time_us; ++ int64_t prev_last_flip_time_us; ++ ++ ClutterFrameHint last_flip_hints; + + /* Last time we promoted short-term maximum to long-term one */ + int64_t longterm_promotion_us; +@@ -245,10 +260,6 @@ static void + maybe_update_longterm_max_duration_us (ClutterFrameClock *frame_clock, + ClutterFrameInfo *frame_info) + { +- /* Do not update long-term max if there has been no measurement */ +- if (!frame_clock->shortterm_max_update_duration_us) +- return; +- + if ((frame_info->presentation_time - frame_clock->longterm_promotion_us) < + G_USEC_PER_SEC) + return; +@@ -275,6 +286,12 @@ void + clutter_frame_clock_notify_presented (ClutterFrameClock *frame_clock, + ClutterFrameInfo *frame_info) + { ++#ifdef CLUTTER_ENABLE_DEBUG ++ const char *debug_state = ++ frame_clock->state == CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO ? ++ "Triple buffering" : "Double buffering"; ++#endif ++ + COGL_TRACE_BEGIN_SCOPED (ClutterFrameClockNotifyPresented, + "Clutter::FrameClock::presented()"); + COGL_TRACE_DESCRIBE (ClutterFrameClockNotifyPresented, +@@ -361,22 +378,52 @@ clutter_frame_clock_notify_presented (ClutterFrameClock *frame_clock, + + frame_clock->got_measurements_last_frame = FALSE; + +- if (frame_info->cpu_time_before_buffer_swap_us != 0 && +- frame_info->has_valid_gpu_rendering_duration) ++ if ((frame_info->cpu_time_before_buffer_swap_us != 0 && ++ frame_info->has_valid_gpu_rendering_duration) || ++ frame_clock->ever_got_measurements) + { + int64_t dispatch_to_swap_us, swap_to_rendering_done_us, swap_to_flip_us; ++ int64_t dispatch_time_us = 0, flip_time_us = 0; + +- dispatch_to_swap_us = +- frame_info->cpu_time_before_buffer_swap_us - +- frame_clock->last_dispatch_time_us; ++ switch (frame_clock->state) ++ { ++ case CLUTTER_FRAME_CLOCK_STATE_INIT: ++ case CLUTTER_FRAME_CLOCK_STATE_IDLE: ++ case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED: ++ case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW: ++ g_warn_if_reached (); ++ G_GNUC_FALLTHROUGH; ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE: ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED: ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW: ++ dispatch_time_us = frame_clock->last_dispatch_time_us; ++ flip_time_us = frame_clock->last_flip_time_us; ++ break; ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO: ++ dispatch_time_us = frame_clock->prev_last_dispatch_time_us; ++ flip_time_us = frame_clock->prev_last_flip_time_us; ++ break; ++ } ++ ++ if (frame_info->cpu_time_before_buffer_swap_us == 0) ++ { ++ /* Cursor-only updates with no "swap" or "flip" */ ++ dispatch_to_swap_us = 0; ++ swap_to_flip_us = 0; ++ } ++ else ++ { ++ dispatch_to_swap_us = frame_info->cpu_time_before_buffer_swap_us - ++ dispatch_time_us; ++ swap_to_flip_us = flip_time_us - ++ frame_info->cpu_time_before_buffer_swap_us; ++ } + swap_to_rendering_done_us = + frame_info->gpu_rendering_duration_ns / 1000; +- swap_to_flip_us = +- frame_clock->last_flip_time_us - +- frame_info->cpu_time_before_buffer_swap_us; + + CLUTTER_NOTE (FRAME_TIMINGS, +- "update2dispatch %ld µs, dispatch2swap %ld µs, swap2render %ld µs, swap2flip %ld µs", ++ "%s: update2dispatch %ld µs, dispatch2swap %ld µs, swap2render %ld µs, swap2flip %ld µs", ++ debug_state, + frame_clock->last_dispatch_lateness_us, + dispatch_to_swap_us, + swap_to_rendering_done_us, +@@ -386,7 +433,7 @@ clutter_frame_clock_notify_presented (ClutterFrameClock *frame_clock, + CLAMP (frame_clock->last_dispatch_lateness_us + dispatch_to_swap_us + + MAX (swap_to_rendering_done_us, swap_to_flip_us), + frame_clock->shortterm_max_update_duration_us, +- frame_clock->refresh_interval_us); ++ 2 * frame_clock->refresh_interval_us); + + maybe_update_longterm_max_duration_us (frame_clock, frame_info); + +@@ -395,7 +442,8 @@ clutter_frame_clock_notify_presented (ClutterFrameClock *frame_clock, + } + else + { +- CLUTTER_NOTE (FRAME_TIMINGS, "update2dispatch %ld µs", ++ CLUTTER_NOTE (FRAME_TIMINGS, "%s: update2dispatch %ld µs", ++ debug_state, + frame_clock->last_dispatch_lateness_us); + } + +@@ -413,11 +461,22 @@ clutter_frame_clock_notify_presented (ClutterFrameClock *frame_clock, + case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW: + g_warn_if_reached (); + break; +- case CLUTTER_FRAME_CLOCK_STATE_DISPATCHING: +- case CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED: ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE: + frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_IDLE; + maybe_reschedule_update (frame_clock); + break; ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED: ++ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED; ++ maybe_reschedule_update (frame_clock); ++ break; ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW: ++ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW; ++ maybe_reschedule_update (frame_clock); ++ break; ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO: ++ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE; ++ maybe_reschedule_update (frame_clock); ++ break; + } + } + +@@ -435,26 +494,37 @@ clutter_frame_clock_notify_ready (ClutterFrameClock *frame_clock) + case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW: + g_warn_if_reached (); + break; +- case CLUTTER_FRAME_CLOCK_STATE_DISPATCHING: +- case CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED: ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE: + frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_IDLE; + maybe_reschedule_update (frame_clock); + break; ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED: ++ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED; ++ maybe_reschedule_update (frame_clock); ++ break; ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW: ++ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW; ++ maybe_reschedule_update (frame_clock); ++ break; ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO: ++ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE; ++ maybe_reschedule_update (frame_clock); ++ break; + } + } + +-static int64_t +-clutter_frame_clock_compute_max_render_time_us (ClutterFrameClock *frame_clock) ++static gboolean ++clutter_frame_clock_compute_max_render_time_us (ClutterFrameClock *frame_clock, ++ int64_t *max_render_time_us) + { + int64_t refresh_interval_us; +- int64_t max_render_time_us; + + refresh_interval_us = frame_clock->refresh_interval_us; + + if (!frame_clock->ever_got_measurements || + G_UNLIKELY (clutter_paint_debug_flags & + CLUTTER_DEBUG_DISABLE_DYNAMIC_MAX_RENDER_TIME)) +- return refresh_interval_us * SYNC_DELAY_FALLBACK_FRACTION; ++ return FALSE; + + /* Max render time shows how early the frame clock needs to be dispatched + * to make it to the predicted next presentation time. It is an estimate of +@@ -468,15 +538,15 @@ clutter_frame_clock_compute_max_render_time_us (ClutterFrameClock *frame_clock) + * - The duration of vertical blank. + * - A constant to account for variations in the above estimates. + */ +- max_render_time_us = ++ *max_render_time_us = + MAX (frame_clock->longterm_max_update_duration_us, + frame_clock->shortterm_max_update_duration_us) + + frame_clock->vblank_duration_us + + clutter_max_render_time_constant_us; + +- max_render_time_us = CLAMP (max_render_time_us, 0, refresh_interval_us); ++ *max_render_time_us = CLAMP (*max_render_time_us, 0, 2 * refresh_interval_us); + +- return max_render_time_us; ++ return TRUE; + } + + static void +@@ -491,7 +561,9 @@ calculate_next_update_time_us (ClutterFrameClock *frame_clock, + int64_t min_render_time_allowed_us; + int64_t max_render_time_allowed_us; + int64_t next_presentation_time_us; ++ int64_t next_smooth_presentation_time_us = 0; + int64_t next_update_time_us; ++ gboolean max_render_time_is_known; + + now_us = g_get_monotonic_time (); + +@@ -511,10 +583,13 @@ calculate_next_update_time_us (ClutterFrameClock *frame_clock, + } + + min_render_time_allowed_us = refresh_interval_us / 2; +- max_render_time_allowed_us = +- clutter_frame_clock_compute_max_render_time_us (frame_clock); + +- if (min_render_time_allowed_us > max_render_time_allowed_us) ++ max_render_time_is_known = ++ clutter_frame_clock_compute_max_render_time_us (frame_clock, ++ &max_render_time_allowed_us); ++ ++ if (max_render_time_is_known && ++ min_render_time_allowed_us > max_render_time_allowed_us) + min_render_time_allowed_us = max_render_time_allowed_us; + + /* +@@ -535,7 +610,28 @@ calculate_next_update_time_us (ClutterFrameClock *frame_clock, + * + */ + last_presentation_time_us = frame_clock->last_presentation_time_us; +- next_presentation_time_us = last_presentation_time_us + refresh_interval_us; ++ switch (frame_clock->state) ++ { ++ case CLUTTER_FRAME_CLOCK_STATE_INIT: ++ case CLUTTER_FRAME_CLOCK_STATE_IDLE: ++ case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED: ++ case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW: ++ next_smooth_presentation_time_us = last_presentation_time_us + ++ refresh_interval_us; ++ break; ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE: ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED: ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW: ++ next_smooth_presentation_time_us = last_presentation_time_us + ++ 2 * refresh_interval_us; ++ break; ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO: ++ next_smooth_presentation_time_us = last_presentation_time_us + ++ 3 * refresh_interval_us; ++ break; ++ } ++ ++ next_presentation_time_us = next_smooth_presentation_time_us; + + /* + * However, the last presentation could have happened more than a frame ago. +@@ -601,7 +697,7 @@ calculate_next_update_time_us (ClutterFrameClock *frame_clock, + } + } + +- if (next_presentation_time_us != last_presentation_time_us + refresh_interval_us) ++ if (next_presentation_time_us != next_smooth_presentation_time_us) + { + /* There was an idle period since the last presentation, so there seems + * be no constantly updating actor. In this case it's best to start +@@ -613,6 +709,24 @@ calculate_next_update_time_us (ClutterFrameClock *frame_clock, + } + else + { ++ /* If the max render time isn't known then using the current value of ++ * next_presentation_time_us is suboptimal. Targeting always one frame ++ * prior to that we'd lose the ability to scale up to triple buffering ++ * on late presentation. But targeting two frames prior we would be ++ * always triple buffering even when not required. ++ * So the algorithm for deciding when to scale up to triple buffering ++ * in the absence of render time measurements is to simply target full ++ * frame rate. If we're keeping up then we'll stay double buffering. If ++ * we're not keeping up then this will switch us to triple buffering. ++ */ ++ if (!max_render_time_is_known) ++ { ++ max_render_time_allowed_us = ++ refresh_interval_us * SYNC_DELAY_FALLBACK_FRACTION; ++ next_presentation_time_us = ++ last_presentation_time_us + refresh_interval_us; ++ } ++ + while (next_presentation_time_us - min_render_time_allowed_us < now_us) + next_presentation_time_us += refresh_interval_us; + +@@ -644,7 +758,9 @@ calculate_next_variable_update_time_us (ClutterFrameClock *frame_clock, + + refresh_interval_us = frame_clock->refresh_interval_us; + +- if (frame_clock->last_presentation_time_us == 0) ++ if (frame_clock->last_presentation_time_us == 0 || ++ !clutter_frame_clock_compute_max_render_time_us (frame_clock, ++ &max_render_time_allowed_us)) + { + *out_next_update_time_us = + frame_clock->last_dispatch_time_us ? +@@ -657,9 +773,6 @@ calculate_next_variable_update_time_us (ClutterFrameClock *frame_clock, + return; + } + +- max_render_time_allowed_us = +- clutter_frame_clock_compute_max_render_time_us (frame_clock); +- + last_presentation_time_us = frame_clock->last_presentation_time_us; + next_presentation_time_us = last_presentation_time_us + refresh_interval_us; + +@@ -733,8 +846,17 @@ clutter_frame_clock_inhibit (ClutterFrameClock *frame_clock) + frame_clock->pending_reschedule_now = TRUE; + frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_IDLE; + break; +- case CLUTTER_FRAME_CLOCK_STATE_DISPATCHING: +- case CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED: ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED: ++ frame_clock->pending_reschedule = TRUE; ++ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE; ++ break; ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW: ++ frame_clock->pending_reschedule = TRUE; ++ frame_clock->pending_reschedule_now = TRUE; ++ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE; ++ break; ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE: ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO: + break; + } + +@@ -770,11 +892,18 @@ clutter_frame_clock_schedule_update_now (ClutterFrameClock *frame_clock) + case CLUTTER_FRAME_CLOCK_STATE_INIT: + case CLUTTER_FRAME_CLOCK_STATE_IDLE: + case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED: ++ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW; + break; + case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW: ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW: + return; +- case CLUTTER_FRAME_CLOCK_STATE_DISPATCHING: +- case CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED: ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE: ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED: ++ next_update_time_us = g_get_monotonic_time (); ++ frame_clock->state = ++ CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW; ++ break; ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO: + frame_clock->pending_reschedule = TRUE; + frame_clock->pending_reschedule_now = TRUE; + return; +@@ -803,13 +932,18 @@ clutter_frame_clock_schedule_update_now (ClutterFrameClock *frame_clock) + + frame_clock->next_update_time_us = next_update_time_us; + g_source_set_ready_time (frame_clock->source, next_update_time_us); +- frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW; + } + + void + clutter_frame_clock_schedule_update (ClutterFrameClock *frame_clock) + { + int64_t next_update_time_us = -1; ++ TripleBufferingMode current_mode = triple_buffering_mode; ++ ++ if (current_mode == TRIPLE_BUFFERING_MODE_AUTO && ++ (frame_clock->last_flip_hints & ++ CLUTTER_FRAME_HINT_DIRECT_SCANOUT_ATTEMPTED)) ++ current_mode = TRIPLE_BUFFERING_MODE_NEVER; + + if (frame_clock->inhibit_count > 0) + { +@@ -825,12 +959,41 @@ clutter_frame_clock_schedule_update (ClutterFrameClock *frame_clock) + frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED; + return; + case CLUTTER_FRAME_CLOCK_STATE_IDLE: ++ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED; + break; + case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED: + case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW: ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED: ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW: + return; +- case CLUTTER_FRAME_CLOCK_STATE_DISPATCHING: +- case CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED: ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE: ++ switch (current_mode) ++ { ++ case TRIPLE_BUFFERING_MODE_NEVER: ++ frame_clock->pending_reschedule = TRUE; ++ return; ++ case TRIPLE_BUFFERING_MODE_AUTO: ++ calculate_next_update_time_us (frame_clock, ++ &next_update_time_us, ++ &frame_clock->next_presentation_time_us, ++ &frame_clock->next_frame_deadline_us); ++ frame_clock->is_next_presentation_time_valid = ++ (frame_clock->next_presentation_time_us != 0); ++ frame_clock->has_next_frame_deadline = ++ (frame_clock->next_frame_deadline_us != 0); ++ frame_clock->state = ++ CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED; ++ break; ++ case TRIPLE_BUFFERING_MODE_ALWAYS: ++ next_update_time_us = g_get_monotonic_time (); ++ frame_clock->next_presentation_time_us = 0; ++ frame_clock->is_next_presentation_time_valid = FALSE; ++ frame_clock->state = ++ CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED; ++ break; ++ } ++ break; ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO: + frame_clock->pending_reschedule = TRUE; + return; + } +@@ -859,7 +1022,6 @@ clutter_frame_clock_schedule_update (ClutterFrameClock *frame_clock) + + frame_clock->next_update_time_us = next_update_time_us; + g_source_set_ready_time (frame_clock->source, next_update_time_us); +- frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED; + } + + void +@@ -875,6 +1037,8 @@ clutter_frame_clock_set_mode (ClutterFrameClock *frame_clock, + { + case CLUTTER_FRAME_CLOCK_STATE_INIT: + case CLUTTER_FRAME_CLOCK_STATE_IDLE: ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE: ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO: + break; + case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED: + frame_clock->pending_reschedule = TRUE; +@@ -885,8 +1049,14 @@ clutter_frame_clock_set_mode (ClutterFrameClock *frame_clock, + frame_clock->pending_reschedule_now = TRUE; + frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_IDLE; + break; +- case CLUTTER_FRAME_CLOCK_STATE_DISPATCHING: +- case CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED: ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED: ++ frame_clock->pending_reschedule = TRUE; ++ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE; ++ break; ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW: ++ frame_clock->pending_reschedule = TRUE; ++ frame_clock->pending_reschedule_now = TRUE; ++ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE; + break; + } + +@@ -922,7 +1092,7 @@ clutter_frame_clock_dispatch (ClutterFrameClock *frame_clock, + frame_clock->refresh_interval_us; + + lateness_us = time_us - ideal_dispatch_time_us; +- if (lateness_us < 0 || lateness_us >= frame_clock->refresh_interval_us) ++ if (lateness_us < 0 || lateness_us >= frame_clock->refresh_interval_us / 4) + frame_clock->last_dispatch_lateness_us = 0; + else + frame_clock->last_dispatch_lateness_us = lateness_us; +@@ -943,10 +1113,27 @@ clutter_frame_clock_dispatch (ClutterFrameClock *frame_clock, + } + #endif + ++ frame_clock->prev_last_dispatch_time_us = frame_clock->last_dispatch_time_us; + frame_clock->last_dispatch_time_us = time_us; + g_source_set_ready_time (frame_clock->source, -1); + +- frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHING; ++ switch (frame_clock->state) ++ { ++ case CLUTTER_FRAME_CLOCK_STATE_INIT: ++ case CLUTTER_FRAME_CLOCK_STATE_IDLE: ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE: ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO: ++ g_warn_if_reached (); ++ return; ++ case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED: ++ case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW: ++ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE; ++ break; ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED: ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW: ++ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO; ++ break; ++ } + + frame_count = frame_clock->frame_count++; + +@@ -977,26 +1164,36 @@ clutter_frame_clock_dispatch (ClutterFrameClock *frame_clock, + result = iface->frame (frame_clock, frame, frame_clock->listener.user_data); + COGL_TRACE_END (ClutterFrameClockFrame); + +- switch (frame_clock->state) ++ switch (result) + { +- case CLUTTER_FRAME_CLOCK_STATE_INIT: +- case CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED: +- g_warn_if_reached (); ++ case CLUTTER_FRAME_RESULT_PENDING_PRESENTED: + break; +- case CLUTTER_FRAME_CLOCK_STATE_IDLE: +- case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED: +- case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW: +- break; +- case CLUTTER_FRAME_CLOCK_STATE_DISPATCHING: +- switch (result) ++ case CLUTTER_FRAME_RESULT_IDLE: ++ /* The frame was aborted; nothing to paint/present */ ++ switch (frame_clock->state) + { +- case CLUTTER_FRAME_RESULT_PENDING_PRESENTED: +- frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED; ++ case CLUTTER_FRAME_CLOCK_STATE_INIT: ++ case CLUTTER_FRAME_CLOCK_STATE_IDLE: ++ case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED: ++ case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW: ++ g_warn_if_reached (); + break; +- case CLUTTER_FRAME_RESULT_IDLE: ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE: + frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_IDLE; + maybe_reschedule_update (frame_clock); + break; ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED: ++ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED; ++ maybe_reschedule_update (frame_clock); ++ break; ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW: ++ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW; ++ maybe_reschedule_update (frame_clock); ++ break; ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO: ++ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE; ++ maybe_reschedule_update (frame_clock); ++ break; + } + break; + } +@@ -1029,21 +1226,31 @@ frame_clock_source_dispatch (GSource *source, + } + + void +-clutter_frame_clock_record_flip_time (ClutterFrameClock *frame_clock, +- int64_t flip_time_us) ++clutter_frame_clock_record_flip (ClutterFrameClock *frame_clock, ++ int64_t flip_time_us, ++ ClutterFrameHint hints) + { ++ frame_clock->prev_last_flip_time_us = frame_clock->last_flip_time_us; + frame_clock->last_flip_time_us = flip_time_us; ++ frame_clock->last_flip_hints = hints; + } + + GString * + clutter_frame_clock_get_max_render_time_debug_info (ClutterFrameClock *frame_clock) + { ++ int64_t max_render_time_us; + int64_t max_update_duration_us; + GString *string; + +- string = g_string_new (NULL); +- g_string_append_printf (string, "Max render time: %ld µs", +- clutter_frame_clock_compute_max_render_time_us (frame_clock)); ++ string = g_string_new ("Max render time: "); ++ if (!clutter_frame_clock_compute_max_render_time_us (frame_clock, ++ &max_render_time_us)) ++ { ++ g_string_append (string, "unknown"); ++ return string; ++ } ++ ++ g_string_append_printf (string, "%ld µs", max_render_time_us); + + if (frame_clock->got_measurements_last_frame) + g_string_append_printf (string, " ="); +@@ -1210,8 +1417,6 @@ clutter_frame_clock_dispose (GObject *object) + { + ClutterFrameClock *frame_clock = CLUTTER_FRAME_CLOCK (object); + +- g_warn_if_fail (frame_clock->state != CLUTTER_FRAME_CLOCK_STATE_DISPATCHING); +- + if (frame_clock->source) + { + g_signal_emit (frame_clock, signals[DESTROY], 0); +@@ -1235,6 +1440,15 @@ static void + clutter_frame_clock_class_init (ClutterFrameClockClass *klass) + { + GObjectClass *object_class = G_OBJECT_CLASS (klass); ++ const char *mode_str; ++ ++ mode_str = g_getenv ("MUTTER_DEBUG_TRIPLE_BUFFERING"); ++ if (!g_strcmp0 (mode_str, "never")) ++ triple_buffering_mode = TRIPLE_BUFFERING_MODE_NEVER; ++ else if (!g_strcmp0 (mode_str, "auto")) ++ triple_buffering_mode = TRIPLE_BUFFERING_MODE_AUTO; ++ else if (!g_strcmp0 (mode_str, "always")) ++ triple_buffering_mode = TRIPLE_BUFFERING_MODE_ALWAYS; + + object_class->dispose = clutter_frame_clock_dispose; + +diff --git a/clutter/clutter/clutter-frame-clock.h b/clutter/clutter/clutter-frame-clock.h +index a7be5ef31..bfc89bde0 100644 +--- a/clutter/clutter/clutter-frame-clock.h ++++ b/clutter/clutter/clutter-frame-clock.h +@@ -33,6 +33,12 @@ typedef enum _ClutterFrameResult + CLUTTER_FRAME_RESULT_IDLE, + } ClutterFrameResult; + ++typedef enum _ClutterFrameHint ++{ ++ CLUTTER_FRAME_HINT_NONE = 0, ++ CLUTTER_FRAME_HINT_DIRECT_SCANOUT_ATTEMPTED = 1 << 0, ++} ClutterFrameHint; ++ + #define CLUTTER_TYPE_FRAME_CLOCK (clutter_frame_clock_get_type ()) + CLUTTER_EXPORT + G_DECLARE_FINAL_TYPE (ClutterFrameClock, clutter_frame_clock, +@@ -102,7 +108,8 @@ void clutter_frame_clock_remove_timeline (ClutterFrameClock *frame_clock, + CLUTTER_EXPORT + float clutter_frame_clock_get_refresh_rate (ClutterFrameClock *frame_clock); + +-void clutter_frame_clock_record_flip_time (ClutterFrameClock *frame_clock, +- int64_t flip_time_us); ++void clutter_frame_clock_record_flip (ClutterFrameClock *frame_clock, ++ int64_t flip_time_us, ++ ClutterFrameHint hints); + + GString * clutter_frame_clock_get_max_render_time_debug_info (ClutterFrameClock *frame_clock); +diff --git a/clutter/clutter/clutter-frame-private.h b/clutter/clutter/clutter-frame-private.h +index ef66b874e..ce140560a 100644 +--- a/clutter/clutter/clutter-frame-private.h ++++ b/clutter/clutter/clutter-frame-private.h +@@ -36,6 +36,7 @@ struct _ClutterFrame + + gboolean has_result; + ClutterFrameResult result; ++ ClutterFrameHint hints; + }; + + CLUTTER_EXPORT +diff --git a/clutter/clutter/clutter-frame.c b/clutter/clutter/clutter-frame.c +index 7436f9f18..53c289b2c 100644 +--- a/clutter/clutter/clutter-frame.c ++++ b/clutter/clutter/clutter-frame.c +@@ -115,3 +115,16 @@ clutter_frame_set_result (ClutterFrame *frame, + frame->result = result; + frame->has_result = TRUE; + } ++ ++void ++clutter_frame_set_hint (ClutterFrame *frame, ++ ClutterFrameHint hint) ++{ ++ frame->hints |= hint; ++} ++ ++ClutterFrameHint ++clutter_frame_get_hints (ClutterFrame *frame) ++{ ++ return frame->hints; ++} +diff --git a/clutter/clutter/clutter-frame.h b/clutter/clutter/clutter-frame.h +index 34f0770bd..c7b3d02ac 100644 +--- a/clutter/clutter/clutter-frame.h ++++ b/clutter/clutter/clutter-frame.h +@@ -54,4 +54,11 @@ void clutter_frame_set_result (ClutterFrame *frame, + CLUTTER_EXPORT + gboolean clutter_frame_has_result (ClutterFrame *frame); + ++CLUTTER_EXPORT ++void clutter_frame_set_hint (ClutterFrame *frame, ++ ClutterFrameHint hint); ++ ++CLUTTER_EXPORT ++ClutterFrameHint clutter_frame_get_hints (ClutterFrame *frame); ++ + G_DEFINE_AUTOPTR_CLEANUP_FUNC (ClutterFrame, clutter_frame_unref) +diff --git a/clutter/clutter/clutter-stage-view.c b/clutter/clutter/clutter-stage-view.c +index b503ef839..1fbe3ae7a 100644 +--- a/clutter/clutter/clutter-stage-view.c ++++ b/clutter/clutter/clutter-stage-view.c +@@ -902,14 +902,21 @@ handle_frame_clock_frame (ClutterFrameClock *frame_clock, + + _clutter_stage_window_redraw_view (stage_window, view, frame); + +- clutter_frame_clock_record_flip_time (frame_clock, +- g_get_monotonic_time ()); ++ clutter_frame_clock_record_flip (frame_clock, ++ g_get_monotonic_time (), ++ clutter_frame_get_hints (frame)); + + clutter_stage_emit_after_paint (stage, view, frame); + + if (_clutter_context_get_show_fps ()) + end_frame_timing_measurement (view); + } ++ else ++ { ++ clutter_frame_clock_record_flip (frame_clock, ++ g_get_monotonic_time (), ++ clutter_frame_get_hints (frame)); ++ } + + _clutter_stage_window_finish_frame (stage_window, view, frame); + +diff --git a/cogl/cogl/cogl-attribute-buffer.c b/cogl/cogl/cogl-attribute-buffer.c +index 55e296dd3..ee89e500a 100644 +--- a/cogl/cogl/cogl-attribute-buffer.c ++++ b/cogl/cogl/cogl-attribute-buffer.c +@@ -51,13 +51,13 @@ cogl_attribute_buffer_init (CoglAttributeBuffer *buffer) + + CoglAttributeBuffer * + cogl_attribute_buffer_new_with_size (CoglContext *context, +- size_t bytes) ++ size_t bytes) + { + CoglAttributeBuffer *buffer; + + buffer = g_object_new (COGL_TYPE_ATTRIBUTE_BUFFER, + "context", context, +- "size", bytes, ++ "size", (uint64_t) bytes, + "default-target", COGL_BUFFER_BIND_TARGET_ATTRIBUTE_BUFFER, + "update-hint", COGL_BUFFER_UPDATE_HINT_STATIC, + NULL); +@@ -67,8 +67,8 @@ cogl_attribute_buffer_new_with_size (CoglContext *context, + + CoglAttributeBuffer * + cogl_attribute_buffer_new (CoglContext *context, +- size_t bytes, +- const void *data) ++ size_t bytes, ++ const void *data) + { + CoglAttributeBuffer *buffer; + +diff --git a/cogl/cogl/cogl-attribute-buffer.h b/cogl/cogl/cogl-attribute-buffer.h +index 45040f7d9..59876a112 100644 +--- a/cogl/cogl/cogl-attribute-buffer.h ++++ b/cogl/cogl/cogl-attribute-buffer.h +@@ -92,7 +92,7 @@ GType cogl_attribute_buffer_get_type (void) G_GNUC_CONST; + */ + COGL_EXPORT CoglAttributeBuffer * + cogl_attribute_buffer_new_with_size (CoglContext *context, +- size_t bytes); ++ size_t bytes); + + /** + * cogl_attribute_buffer_new: +@@ -122,7 +122,7 @@ cogl_attribute_buffer_new_with_size (CoglContext *context, + */ + COGL_EXPORT CoglAttributeBuffer * + cogl_attribute_buffer_new (CoglContext *context, +- size_t bytes, +- const void *data); ++ size_t bytes, ++ const void *data); + + G_END_DECLS +diff --git a/cogl/cogl/cogl-context-private.h b/cogl/cogl/cogl-context-private.h +index 4c0bc4bea..cd5db0ee3 100644 +--- a/cogl/cogl/cogl-context-private.h ++++ b/cogl/cogl/cogl-context-private.h +@@ -311,3 +311,6 @@ _cogl_context_set_current_projection_entry (CoglContext *context, + void + _cogl_context_set_current_modelview_entry (CoglContext *context, + CoglMatrixEntry *entry); ++ ++void ++_cogl_context_update_sync (CoglContext *context); +diff --git a/cogl/cogl/cogl-context.c b/cogl/cogl/cogl-context.c +index be13933ec..492d49542 100644 +--- a/cogl/cogl/cogl-context.c ++++ b/cogl/cogl/cogl-context.c +@@ -463,6 +463,28 @@ _cogl_context_set_current_modelview_entry (CoglContext *context, + context->current_modelview_entry = entry; + } + ++void ++_cogl_context_update_sync (CoglContext *context) ++{ ++ const CoglWinsysVtable *winsys = _cogl_context_get_winsys (context); ++ ++ if (!winsys->update_sync) ++ return; ++ ++ winsys->update_sync (context); ++} ++ ++int ++cogl_context_get_latest_sync_fd (CoglContext *context) ++{ ++ const CoglWinsysVtable *winsys = _cogl_context_get_winsys (context); ++ ++ if (!winsys->get_sync_fd) ++ return -1; ++ ++ return winsys->get_sync_fd (context); ++} ++ + CoglGraphicsResetStatus + cogl_get_graphics_reset_status (CoglContext *context) + { +diff --git a/cogl/cogl/cogl-context.h b/cogl/cogl/cogl-context.h +index 614a351fe..fba7bedf2 100644 +--- a/cogl/cogl/cogl-context.h ++++ b/cogl/cogl/cogl-context.h +@@ -367,4 +367,18 @@ cogl_context_timestamp_query_get_time_ns (CoglContext *context, + COGL_EXPORT int64_t + cogl_context_get_gpu_time_ns (CoglContext *context); + ++/** ++ * cogl_context_get_latest_sync_fd ++ * @context: a #CoglContext pointer ++ * ++ * This function is used to get support for waiting on previous ++ * GPU work through sync fds. It will return a sync fd which will ++ * signal when the previous work has completed. ++ * ++ * Return value: sync fd for latest GPU submission if available, ++ * returns -1 if not. ++ */ ++COGL_EXPORT int ++cogl_context_get_latest_sync_fd (CoglContext *context); ++ + G_END_DECLS +diff --git a/cogl/cogl/cogl-index-buffer.c b/cogl/cogl/cogl-index-buffer.c +index 4de5205d3..1e4309436 100644 +--- a/cogl/cogl/cogl-index-buffer.c ++++ b/cogl/cogl/cogl-index-buffer.c +@@ -53,13 +53,14 @@ cogl_index_buffer_init (CoglIndexBuffer *buffer) + * indices buffer should be able to contain multiple ranges of indices + * which the wiki design doesn't currently consider. */ + CoglIndexBuffer * +-cogl_index_buffer_new (CoglContext *context, size_t bytes) ++cogl_index_buffer_new (CoglContext *context, ++ size_t bytes) + { + CoglIndexBuffer *indices; + + indices = g_object_new (COGL_TYPE_INDEX_BUFFER, + "context", context, +- "size", bytes, ++ "size", (uint64_t) bytes, + "default-target", COGL_BUFFER_BIND_TARGET_INDEX_BUFFER, + "update-hint", COGL_BUFFER_UPDATE_HINT_STATIC, + NULL); +diff --git a/cogl/cogl/cogl-index-buffer.h b/cogl/cogl/cogl-index-buffer.h +index 23b75d837..ec7418318 100644 +--- a/cogl/cogl/cogl-index-buffer.h ++++ b/cogl/cogl/cogl-index-buffer.h +@@ -78,6 +78,6 @@ GType cogl_index_buffer_get_type (void) G_GNUC_CONST; + */ + COGL_EXPORT CoglIndexBuffer * + cogl_index_buffer_new (CoglContext *context, +- size_t bytes); ++ size_t bytes); + + G_END_DECLS +diff --git a/cogl/cogl/cogl-onscreen-private.h b/cogl/cogl/cogl-onscreen-private.h +index 959a60533..86d8ea2d5 100644 +--- a/cogl/cogl/cogl-onscreen-private.h ++++ b/cogl/cogl/cogl-onscreen-private.h +@@ -78,4 +78,7 @@ COGL_EXPORT CoglFrameInfo * + cogl_onscreen_peek_tail_frame_info (CoglOnscreen *onscreen); + + COGL_EXPORT CoglFrameInfo * +-cogl_onscreen_pop_head_frame_info (CoglOnscreen *onscreen); ++cogl_onscreen_pop_head_frame_info (CoglOnscreen *onscreen); ++ ++COGL_EXPORT unsigned int ++cogl_onscreen_count_pending_frames (CoglOnscreen *onscreen); +diff --git a/cogl/cogl/cogl-onscreen.c b/cogl/cogl/cogl-onscreen.c +index f4b460a83..086be7ed7 100644 +--- a/cogl/cogl/cogl-onscreen.c ++++ b/cogl/cogl/cogl-onscreen.c +@@ -325,6 +325,7 @@ cogl_onscreen_swap_buffers_with_damage (CoglOnscreen *onscreen, + { + CoglOnscreenPrivate *priv = cogl_onscreen_get_instance_private (onscreen); + CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen); ++ CoglContext *context = cogl_framebuffer_get_context (framebuffer); + CoglOnscreenClass *klass = COGL_ONSCREEN_GET_CLASS (onscreen); + + g_return_if_fail (COGL_IS_ONSCREEN (framebuffer)); +@@ -334,6 +335,9 @@ cogl_onscreen_swap_buffers_with_damage (CoglOnscreen *onscreen, + + _cogl_framebuffer_flush_journal (framebuffer); + ++ /* Update our "latest" sync fd to contain all previous work */ ++ _cogl_context_update_sync (context); ++ + if (G_UNLIKELY (COGL_DEBUG_ENABLED (COGL_DEBUG_SYNC_FRAME))) + cogl_framebuffer_finish (framebuffer); + +@@ -511,6 +515,14 @@ cogl_onscreen_pop_head_frame_info (CoglOnscreen *onscreen) + return g_queue_pop_head (&priv->pending_frame_infos); + } + ++unsigned int ++cogl_onscreen_count_pending_frames (CoglOnscreen *onscreen) ++{ ++ CoglOnscreenPrivate *priv = cogl_onscreen_get_instance_private (onscreen); ++ ++ return g_queue_get_length (&priv->pending_frame_infos); ++} ++ + CoglFrameClosure * + cogl_onscreen_add_frame_callback (CoglOnscreen *onscreen, + CoglFrameCallback callback, +diff --git a/cogl/cogl/cogl-pixel-buffer.c b/cogl/cogl/cogl-pixel-buffer.c +index fc238580a..392460933 100644 +--- a/cogl/cogl/cogl-pixel-buffer.c ++++ b/cogl/cogl/cogl-pixel-buffer.c +@@ -63,15 +63,15 @@ cogl_pixel_buffer_init (CoglPixelBuffer *buffer) + + static CoglPixelBuffer * + _cogl_pixel_buffer_new (CoglContext *context, +- size_t size, +- const void *data, +- GError **error) ++ size_t size, ++ const void *data, ++ GError **error) + { + CoglPixelBuffer *pixel_buffer; + + pixel_buffer = g_object_new (COGL_TYPE_PIXEL_BUFFER, + "context", context, +- "size", size, ++ "size", (uint64_t) size, + "default-target", COGL_BUFFER_BIND_TARGET_PIXEL_UNPACK, + "update-hint", COGL_BUFFER_UPDATE_HINT_STATIC, + NULL); +@@ -94,8 +94,8 @@ _cogl_pixel_buffer_new (CoglContext *context, + + CoglPixelBuffer * + cogl_pixel_buffer_new (CoglContext *context, +- size_t size, +- const void *data) ++ size_t size, ++ const void *data) + { + GError *ignore_error = NULL; + CoglPixelBuffer *buffer = +diff --git a/cogl/cogl/cogl-pixel-buffer.h b/cogl/cogl/cogl-pixel-buffer.h +index e7abe0888..cf26a61b2 100644 +--- a/cogl/cogl/cogl-pixel-buffer.h ++++ b/cogl/cogl/cogl-pixel-buffer.h +@@ -87,7 +87,7 @@ GType cogl_pixel_buffer_get_type (void) G_GNUC_CONST; + */ + COGL_EXPORT CoglPixelBuffer * + cogl_pixel_buffer_new (CoglContext *context, +- size_t size, +- const void *data); ++ size_t size, ++ const void *data); + + G_END_DECLS +diff --git a/cogl/cogl/meson.build b/cogl/cogl/meson.build +index d1aa6ef1d..261edbd86 100644 +--- a/cogl/cogl/meson.build ++++ b/cogl/cogl/meson.build +@@ -407,6 +407,7 @@ libmutter_cogl = shared_library(libmutter_cogl_name, + install: true, + ) + libmutter_cogl_dep = declare_dependency( ++ sources: [cogl_enums[1]], + dependencies: [cogl_deps], + link_with: libmutter_cogl, + ) +diff --git a/cogl/cogl/winsys/cogl-onscreen-egl.c b/cogl/cogl/winsys/cogl-onscreen-egl.c +index 6c5bf2654..44010969c 100644 +--- a/cogl/cogl/winsys/cogl-onscreen-egl.c ++++ b/cogl/cogl/winsys/cogl-onscreen-egl.c +@@ -281,6 +281,27 @@ cogl_onscreen_egl_queue_damage_region (CoglOnscreen *onscreen, + g_warning ("Error reported by eglSetDamageRegion"); + } + ++void ++cogl_onscreen_egl_maybe_create_timestamp_query (CoglOnscreen *onscreen, ++ CoglFrameInfo *info) ++{ ++ CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen); ++ CoglContext *context = cogl_framebuffer_get_context (framebuffer); ++ ++ if (!cogl_has_feature (context, COGL_FEATURE_ID_TIMESTAMP_QUERY)) ++ return; ++ ++ info->gpu_time_before_buffer_swap_ns = ++ cogl_context_get_gpu_time_ns (context); ++ info->cpu_time_before_buffer_swap_us = g_get_monotonic_time (); ++ ++ /* Set up a timestamp query for when all rendering will be finished. */ ++ info->timestamp_query = ++ cogl_framebuffer_create_timestamp_query (framebuffer); ++ ++ info->has_valid_gpu_rendering_duration = TRUE; ++} ++ + static void + cogl_onscreen_egl_swap_buffers_with_damage (CoglOnscreen *onscreen, + const int *rectangles, +@@ -309,19 +330,6 @@ cogl_onscreen_egl_swap_buffers_with_damage (CoglOnscreen *onscreen, + COGL_FRAMEBUFFER (onscreen), + COGL_FRAMEBUFFER_STATE_BIND); + +- if (cogl_has_feature (context, COGL_FEATURE_ID_TIMESTAMP_QUERY)) +- { +- info->gpu_time_before_buffer_swap_ns = +- cogl_context_get_gpu_time_ns (context); +- info->cpu_time_before_buffer_swap_us = g_get_monotonic_time (); +- +- /* Set up a timestamp query for when all rendering will be finished. */ +- info->timestamp_query = +- cogl_framebuffer_create_timestamp_query (COGL_FRAMEBUFFER (onscreen)); +- +- info->has_valid_gpu_rendering_duration = TRUE; +- } +- + if (n_rectangles && priv->pf_eglSwapBuffersWithDamage) + { + CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen); +diff --git a/cogl/cogl/winsys/cogl-onscreen-egl.h b/cogl/cogl/winsys/cogl-onscreen-egl.h +index 13662be60..d4dda6636 100644 +--- a/cogl/cogl/winsys/cogl-onscreen-egl.h ++++ b/cogl/cogl/winsys/cogl-onscreen-egl.h +@@ -40,6 +40,10 @@ struct _CoglOnscreenEglClass + CoglOnscreenClass parent_class; + }; + ++COGL_EXPORT void ++cogl_onscreen_egl_maybe_create_timestamp_query (CoglOnscreen *onscreen, ++ CoglFrameInfo *info); ++ + COGL_EXPORT void + cogl_onscreen_egl_set_egl_surface (CoglOnscreenEgl *onscreen_egl, + EGLSurface egl_surface); +diff --git a/cogl/cogl/winsys/cogl-winsys-egl-feature-functions.h b/cogl/cogl/winsys/cogl-winsys-egl-feature-functions.h +index 4b74e2088..a0d03a3c4 100644 +--- a/cogl/cogl/winsys/cogl-winsys-egl-feature-functions.h ++++ b/cogl/cogl/winsys/cogl-winsys-egl-feature-functions.h +@@ -162,6 +162,15 @@ COGL_WINSYS_FEATURE_FUNCTION (EGLBoolean, eglDestroySync, + (EGLDisplay dpy, + EGLSyncKHR sync)) + COGL_WINSYS_FEATURE_END () ++ ++COGL_WINSYS_FEATURE_BEGIN (native_fence_sync, ++ "ANDROID\0", ++ "native_fence_sync\0", ++ COGL_EGL_WINSYS_FEATURE_NATIVE_FENCE_SYNC) ++COGL_WINSYS_FEATURE_FUNCTION (EGLint, eglDupNativeFenceFD, ++ (EGLDisplay dpy, ++ EGLSyncKHR sync)) ++COGL_WINSYS_FEATURE_END () + #endif + + COGL_WINSYS_FEATURE_BEGIN (surfaceless_context, +diff --git a/cogl/cogl/winsys/cogl-winsys-egl-private.h b/cogl/cogl/winsys/cogl-winsys-egl-private.h +index 9b0b168b4..6a31d1ab2 100644 +--- a/cogl/cogl/winsys/cogl-winsys-egl-private.h ++++ b/cogl/cogl/winsys/cogl-winsys-egl-private.h +@@ -101,6 +101,7 @@ typedef enum _CoglEGLWinsysFeature + COGL_EGL_WINSYS_FEATURE_SURFACELESS_CONTEXT = 1L << 6, + COGL_EGL_WINSYS_FEATURE_CONTEXT_PRIORITY = 1L << 7, + COGL_EGL_WINSYS_FEATURE_NO_CONFIG_CONTEXT = 1L << 8, ++ COGL_EGL_WINSYS_FEATURE_NATIVE_FENCE_SYNC = 1L << 9, + } CoglEGLWinsysFeature; + + typedef struct _CoglRendererEGL +@@ -119,6 +120,9 @@ typedef struct _CoglRendererEGL + /* vtable for platform specific parts */ + const CoglWinsysEGLVtable *platform_vtable; + ++ /* Sync for latest submitted work */ ++ EGLSyncKHR sync; ++ + /* Function pointers for EGL specific extensions */ + #define COGL_WINSYS_FEATURE_BEGIN(a, b, c, d) + +diff --git a/cogl/cogl/winsys/cogl-winsys-egl-x11.c b/cogl/cogl/winsys/cogl-winsys-egl-x11.c +index 5472ba9d9..7a52129c2 100644 +--- a/cogl/cogl/winsys/cogl-winsys-egl-x11.c ++++ b/cogl/cogl/winsys/cogl-winsys-egl-x11.c +@@ -243,6 +243,7 @@ _cogl_winsys_renderer_connect (CoglRenderer *renderer, + xlib_renderer = _cogl_xlib_renderer_get_data (renderer); + + egl_renderer->platform_vtable = &_cogl_winsys_egl_vtable; ++ egl_renderer->sync = EGL_NO_SYNC_KHR; + + if (!_cogl_xlib_renderer_connect (renderer, error)) + goto error; +diff --git a/cogl/cogl/winsys/cogl-winsys-egl.c b/cogl/cogl/winsys/cogl-winsys-egl.c +index ee7c1df54..66b9940c0 100644 +--- a/cogl/cogl/winsys/cogl-winsys-egl.c ++++ b/cogl/cogl/winsys/cogl-winsys-egl.c +@@ -441,6 +441,9 @@ _cogl_winsys_display_destroy (CoglDisplay *display) + + g_return_if_fail (egl_display != NULL); + ++ if (egl_renderer->sync != EGL_NO_SYNC_KHR) ++ egl_renderer->pf_eglDestroySync (egl_renderer->edpy, egl_renderer->sync); ++ + cleanup_context (display); + + if (egl_renderer->platform_vtable->display_destroy) +@@ -573,6 +576,37 @@ _cogl_winsys_fence_destroy (CoglContext *context, void *fence) + + renderer->pf_eglDestroySync (renderer->edpy, fence); + } ++ ++static int ++_cogl_winsys_get_sync_fd (CoglContext *context) ++{ ++ CoglRendererEGL *renderer = context->display->renderer->winsys; ++ int fd; ++ ++ if (!renderer->pf_eglDupNativeFenceFD) ++ return -1; ++ ++ fd = renderer->pf_eglDupNativeFenceFD (renderer->edpy, renderer->sync); ++ if (fd == EGL_NO_NATIVE_FENCE_FD_ANDROID) ++ return -1; ++ ++ return fd; ++} ++ ++static void ++_cogl_winsys_update_sync (CoglContext *context) ++{ ++ CoglRendererEGL *renderer = context->display->renderer->winsys; ++ ++ if (!renderer->pf_eglDestroySync || !renderer->pf_eglCreateSync) ++ return; ++ ++ if (renderer->sync != EGL_NO_SYNC_KHR) ++ renderer->pf_eglDestroySync (renderer->edpy, renderer->sync); ++ ++ renderer->sync = renderer->pf_eglCreateSync (renderer->edpy, ++ EGL_SYNC_NATIVE_FENCE_ANDROID, NULL); ++} + #endif + + static CoglWinsysVtable _cogl_winsys_vtable = +@@ -595,6 +629,8 @@ static CoglWinsysVtable _cogl_winsys_vtable = + .fence_add = _cogl_winsys_fence_add, + .fence_is_complete = _cogl_winsys_fence_is_complete, + .fence_destroy = _cogl_winsys_fence_destroy, ++ .get_sync_fd = _cogl_winsys_get_sync_fd, ++ .update_sync = _cogl_winsys_update_sync, + #endif + }; + +diff --git a/cogl/cogl/winsys/cogl-winsys-private.h b/cogl/cogl/winsys/cogl-winsys-private.h +index 7dfbd13ed..d627617d6 100644 +--- a/cogl/cogl/winsys/cogl-winsys-private.h ++++ b/cogl/cogl/winsys/cogl-winsys-private.h +@@ -141,6 +141,12 @@ typedef struct _CoglWinsysVtable + (*fence_destroy) (CoglContext *ctx, + void *fence); + ++ void ++ (*update_sync) (CoglContext *ctx); ++ ++ int ++ (*get_sync_fd) (CoglContext *ctx); ++ + } CoglWinsysVtable; + + typedef const CoglWinsysVtable *(*CoglWinsysVtableGetter) (void); +diff --git a/config.h.meson b/config.h.meson +index 094d79616..9919bd4d0 100644 +--- a/config.h.meson ++++ b/config.h.meson +@@ -135,3 +135,6 @@ + + /* Supports malloc_trim */ + #mesondefine HAVE_MALLOC_TRIM ++ ++/* Supports eventfd */ ++#mesondefine HAVE_EVENTFD +diff --git a/data/dbus-interfaces/org.gnome.Mutter.DisplayConfig.xml b/data/dbus-interfaces/org.gnome.Mutter.DisplayConfig.xml +index 86421b7b0..7294c57a8 100644 +--- a/data/dbus-interfaces/org.gnome.Mutter.DisplayConfig.xml ++++ b/data/dbus-interfaces/org.gnome.Mutter.DisplayConfig.xml +@@ -396,8 +396,8 @@ + @layout_mode current layout mode represents the way logical monitors + are laid out on the screen. Possible modes include: + +- 1 : physical +- 2 : logical ++ 1 : logical ++ 2 : physical + + With physical layout mode, each logical monitor has the same dimensions + as the monitor modes of the associated monitors assigned to it, no +diff --git a/meson.build b/meson.build +index c5ec57395..9732d8460 100644 +--- a/meson.build ++++ b/meson.build +@@ -541,6 +541,8 @@ endif + + cc.compiles('void main (void) { __builtin_ffsl (0); __builtin_popcountl (0); }') + ++have_eventfd = cc.has_header('sys/eventfd.h') ++ + cdata = configuration_data() + cdata.set_quoted('GETTEXT_PACKAGE', gettext_package) + cdata.set_quoted('VERSION', meson.project_version()) +@@ -573,6 +575,7 @@ cdata.set('HAVE_LIBDISPLAY_INFO', have_libdisplay_info) + cdata.set('HAVE_PANGO_FT2', have_pango_ft2) + cdata.set('HAVE_TIMERFD', have_timerfd) + cdata.set('HAVE_MALLOC_TRIM', have_malloc_trim) ++cdata.set('HAVE_EVENTFD', have_eventfd) + + if have_x11_client + xkb_base = xkeyboard_config_dep.get_variable('xkb_base') +diff --git a/mtk/mtk/mtk-rectangle.c b/mtk/mtk/mtk-rectangle.c +index 55626d8e2..4f5927ccc 100644 +--- a/mtk/mtk/mtk-rectangle.c ++++ b/mtk/mtk/mtk-rectangle.c +@@ -65,6 +65,12 @@ mtk_rectangle_new (int x, + return rect; + } + ++MtkRectangle * ++mtk_rectangle_new_empty (void) ++{ ++ return g_new0 (MtkRectangle, 1); ++} ++ + /** + * mtk_rectangle_area: + * @rect: A rectangle +diff --git a/mtk/mtk/mtk-rectangle.h b/mtk/mtk/mtk-rectangle.h +index 2463d3b08..1cb598fce 100644 +--- a/mtk/mtk/mtk-rectangle.h ++++ b/mtk/mtk/mtk-rectangle.h +@@ -86,6 +86,9 @@ MtkRectangle * mtk_rectangle_new (int x, + int width, + int height); + ++MTK_EXPORT ++MtkRectangle * mtk_rectangle_new_empty (void); ++ + /* Basic comparison functions */ + MTK_EXPORT + int mtk_rectangle_area (const MtkRectangle *rect); +@@ -149,3 +152,5 @@ void mtk_rectangle_scale_double (const MtkRectangle *rect, + MTK_EXPORT + gboolean mtk_rectangle_is_adjacent_to (const MtkRectangle *rect, + const MtkRectangle *other); ++ ++G_DEFINE_AUTOPTR_CLEANUP_FUNC (MtkRectangle, mtk_rectangle_free) +diff --git a/po/LINGUAS b/po/LINGUAS +index 02ae4c496..b89761cb3 100644 +--- a/po/LINGUAS ++++ b/po/LINGUAS +@@ -47,6 +47,7 @@ is + it + ja + ka ++kab + kk + kn + ko +diff --git a/po/kab.po b/po/kab.po +new file mode 100644 +index 000000000..5cc29556c +--- /dev/null ++++ b/po/kab.po +@@ -0,0 +1,793 @@ ++# Kabyle translation for mutter. ++# Copyright (C) 2024 mutter's COPYRIGHT HOLDER ++# This file is distributed under the same license as the mutter package. ++# FIRST AUTHOR , YEAR. ++# ButterflyOfFire , 2024. ++# ++msgid "" ++msgstr "" ++"Project-Id-Version: mutter main\n" ++"Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/mutter/issues/\n" ++"POT-Creation-Date: 2024-04-04 12:25+0000\n" ++"PO-Revision-Date: 2024-04-08 06:56+0200\n" ++"Last-Translator: sa\n" ++"Language-Team: Kabyle <>\n" ++"Language: kab\n" ++"MIME-Version: 1.0\n" ++"Content-Type: text/plain; charset=UTF-8\n" ++"Content-Transfer-Encoding: 8bit\n" ++"Plural-Forms: nplurals=2; plural=n>1;\n" ++"X-DL-Team: kab\n" ++"X-DL-Module: mutter\n" ++"X-DL-Branch: main\n" ++"X-DL-Domain: po\n" ++"X-DL-State: Translating\n" ++"X-Generator: Poedit 3.4.2\n" ++ ++#: data/50-mutter-navigation.xml:6 ++msgid "Navigation" ++msgstr "Tunigin" ++ ++#: data/50-mutter-navigation.xml:9 ++msgid "Move window to workspace 1" ++msgstr "Smutti asfaylu ɣer tallunt umahil tis 1" ++ ++#: data/50-mutter-navigation.xml:12 ++msgid "Move window to workspace 2" ++msgstr "Smutti asfaylu ɣer tallunt umahil tis 2" ++ ++#: data/50-mutter-navigation.xml:15 ++msgid "Move window to workspace 3" ++msgstr "Smutti asfaylu ɣer tallunt umahil tis 3" ++ ++#: data/50-mutter-navigation.xml:18 ++msgid "Move window to workspace 4" ++msgstr "Smutti asfaylu ɣer tallunt umahil tis 3" ++ ++#: data/50-mutter-navigation.xml:21 ++msgid "Move window to last workspace" ++msgstr "Smutti asfaylu ɣer tallunt umahil" ++ ++#: data/50-mutter-navigation.xml:24 ++msgid "Move window one workspace to the left" ++msgstr "Ssiweḍ asfaylu yiwet n tallunt n umahil yellan ɣer tama tazelmaḍt" ++ ++#: data/50-mutter-navigation.xml:27 ++msgid "Move window one workspace to the right" ++msgstr "Ssiweḍ asfaylu yiwet n tallunt n umahil yellan ɣer tama tayeffust" ++ ++#: data/50-mutter-navigation.xml:31 ++msgid "Move window one workspace up" ++msgstr "Ssiweḍ asfaylu yiwet n tallunt n umahil yellan d asawen" ++ ++#: data/50-mutter-navigation.xml:35 ++msgid "Move window one workspace down" ++msgstr "Ssiweḍ asfaylu yiwet n tallunt n umahil yellan d akessar" ++ ++#: data/50-mutter-navigation.xml:38 ++msgid "Move window one monitor to the left" ++msgstr "Senkez asfaylu s yiwen usefrak ɣer uzelmaḍ" ++ ++#: data/50-mutter-navigation.xml:41 ++msgid "Move window one monitor to the right" ++msgstr "Senkez asfaylu s yiwen usefrak ɣer uyeffus" ++ ++#: data/50-mutter-navigation.xml:44 ++msgid "Move window one monitor up" ++msgstr "Senkez asfaylu s yiwen usefrak d asawen" ++ ++#: data/50-mutter-navigation.xml:47 ++msgid "Move window one monitor down" ++msgstr "Senkez asfaylu s yiwen usefrak d akessar" ++ ++#: data/50-mutter-navigation.xml:51 ++msgid "Switch applications" ++msgstr "Senfel isnasen" ++ ++#: data/50-mutter-navigation.xml:56 ++msgid "Switch to previous application" ++msgstr "Uɣal ɣer usnas yezrin" ++ ++#: data/50-mutter-navigation.xml:60 ++msgid "Switch windows" ++msgstr "Senfel isfuyla" ++ ++#: data/50-mutter-navigation.xml:65 ++msgid "Switch to previous window" ++msgstr "Uɣal ɣer usfaylu yezrin" ++ ++#: data/50-mutter-navigation.xml:69 ++msgid "Switch windows of an application" ++msgstr "Senfel isfuyla n usnas" ++ ++#: data/50-mutter-navigation.xml:74 ++msgid "Switch to previous window of an application" ++msgstr "Uɣal ɣer usfaylu yezrin n usnas" ++ ++#: data/50-mutter-navigation.xml:78 ++msgid "Switch system controls" ++msgstr "Senfel isenqaden n unagraw" ++ ++#: data/50-mutter-navigation.xml:83 ++msgid "Switch to previous system control" ++msgstr "Uɣal ɣer usenqad n unagraw yezrin" ++ ++#: data/50-mutter-navigation.xml:87 ++msgid "Switch windows directly" ++msgstr "Senfel srid isfuyla" ++ ++#: data/50-mutter-navigation.xml:92 ++msgid "Switch directly to previous window" ++msgstr "Uɣam srid ɣer usfaylu yezrin" ++ ++#: data/50-mutter-navigation.xml:96 ++msgid "Switch windows of an app directly" ++msgstr "Senfel srid isfuyla n usnas" ++ ++#: data/50-mutter-navigation.xml:101 ++msgid "Switch directly to previous window of an app" ++msgstr "Uɣal srid usfaylu yezrin n usnas" ++ ++#: data/50-mutter-navigation.xml:105 ++msgid "Switch system controls directly" ++msgstr "Senfel srid isenqaden n ungraw" ++ ++#: data/50-mutter-navigation.xml:110 ++msgid "Switch directly to previous system control" ++msgstr "Uɣal srid ɣer usenqad n ungraw yezrin" ++ ++#: data/50-mutter-navigation.xml:113 ++msgid "Hide all normal windows" ++msgstr "Ffer akk isfuyla imugna" ++ ++#: data/50-mutter-navigation.xml:116 ++msgid "Switch to workspace 1" ++msgstr "Ddu ɣer tallunt n umahil tis 1" ++ ++#: data/50-mutter-navigation.xml:119 ++msgid "Switch to workspace 2" ++msgstr "Ddu ɣer tallunt n umahil tis 2" ++ ++#: data/50-mutter-navigation.xml:122 ++msgid "Switch to workspace 3" ++msgstr "Ddu ɣer tallunt n umahil tis 3" ++ ++#: data/50-mutter-navigation.xml:125 ++msgid "Switch to workspace 4" ++msgstr "Ddu ɣer tallunt n umahil tis 4" ++ ++#: data/50-mutter-navigation.xml:128 ++msgid "Switch to last workspace" ++msgstr "Ddu ɣer tallunt n umahil taneggarut" ++ ++#: data/50-mutter-navigation.xml:131 ++msgid "Switch to workspace on the left" ++msgstr "Ddu ɣer tallunt n umahil deg tama taẓelmaḍt" ++ ++#: data/50-mutter-navigation.xml:134 ++msgid "Switch to workspace on the right" ++msgstr "Ddu ɣer tallunt n umahil deg tama tayeffust" ++ ++#: data/50-mutter-navigation.xml:138 ++msgid "Switch to workspace above" ++msgstr "Ddu ɣer tallunt n umahil ufella" ++ ++#: data/50-mutter-navigation.xml:142 ++msgid "Switch to workspace below" ++msgstr "Ddu ɣer tallunt n umahil wadda" ++ ++#: data/50-mutter-system.xml:6 data/50-mutter-wayland.xml:6 ++msgid "System" ++msgstr "Anagraw" ++ ++#: data/50-mutter-system.xml:8 ++msgid "Show the run command prompt" ++msgstr "Sken aneftay n tladna n uselkem" ++ ++#: data/50-mutter-wayland.xml:8 ++msgid "Restore the keyboard shortcuts" ++msgstr "Err-d inegzumen n unasiw" ++ ++#: data/50-mutter-windows.xml:6 ++msgid "Windows" ++msgstr "Isfuyla" ++ ++#: data/50-mutter-windows.xml:8 ++msgid "Activate the window menu" ++msgstr "Rmed umuɣ n usfaylu" ++ ++#: data/50-mutter-windows.xml:10 ++msgid "Toggle fullscreen mode" ++msgstr "Ldi/Mdel askar n ugdil aččuran" ++ ++#: data/50-mutter-windows.xml:12 ++msgid "Toggle maximization state" ++msgstr "Ldi/Mdel addad n usumɣer" ++ ++#: data/50-mutter-windows.xml:14 ++msgid "Maximize window" ++msgstr "Semɣer asfaylu" ++ ++#: data/50-mutter-windows.xml:16 ++msgid "Restore window" ++msgstr "Err-d asfaylu" ++ ++#: data/50-mutter-windows.xml:18 ++msgid "Close window" ++msgstr "Mdel asfaylu" ++ ++#: data/50-mutter-windows.xml:20 ++msgid "Hide window" ++msgstr "Ffer asfaylu" ++ ++#: data/50-mutter-windows.xml:22 ++msgid "Move window" ++msgstr "Senkez asfaylu" ++ ++#: data/50-mutter-windows.xml:24 ++msgid "Resize window" ++msgstr "Semsawi teɣzi n usfaylu" ++ ++#: data/50-mutter-windows.xml:27 ++msgid "Toggle window on all workspaces or one" ++msgstr "Ldi/Mdel asfaylu deg meṛṛa tallunin n umahil neɣ deg yiwet kan" ++ ++#: data/50-mutter-windows.xml:29 ++msgid "Raise window if covered, otherwise lower it" ++msgstr "Sali asfaylu ma yella iɣumm, neɣ ṣbb-it" ++ ++#: data/50-mutter-windows.xml:31 ++msgid "Raise window above other windows" ++msgstr "Sezmer asfaylu yellan nnig yisfuyla niḍen" ++ ++#: data/50-mutter-windows.xml:33 ++msgid "Lower window below other windows" ++msgstr "Asfaylu n wadda ddaw n yisfuyla niḍen" ++ ++#: data/50-mutter-windows.xml:35 ++msgid "Maximize window vertically" ++msgstr "Semɣer asfaylu s wudem ubdid" ++ ++#: data/50-mutter-windows.xml:37 ++msgid "Maximize window horizontally" ++msgstr "Semɣer asfaylu s wudem aglawan" ++ ++#: data/50-mutter-windows.xml:41 data/org.gnome.mutter.gschema.xml.in:167 ++msgid "View split on left" ++msgstr "Sken beṭṭu ɣer uzelmaḍ" ++ ++#: data/50-mutter-windows.xml:45 data/org.gnome.mutter.gschema.xml.in:172 ++msgid "View split on right" ++msgstr "Sken beṭṭu ɣer uyeffus" ++ ++#: data/org.gnome.mutter.gschema.xml.in:15 ++msgid "Modifier to use for extended window management operations" ++msgstr "Aneglam ara yettwasqedcen i temhilin ɣezzifen n usefrek n yisfuyla" ++ ++#: data/org.gnome.mutter.gschema.xml.in:16 ++msgid "" ++"This key will initiate the “overlay”, which is a combination window overview " ++"and application launching system. The default is intended to be the “Windows " ++"key” on PC hardware. It’s expected that this binding either the default or " ++"set to the empty string." ++msgstr "" ++"Tasarut-a ad twennez \"asembibbi\", d anagraw n usuddes n teskant n yisfuyla " ++"akked uskar n usnas. Azal amezwer d \"tasarut n Windows\" ɣef warrum PC. " ++"Yetturaǧu tasarut-a ad tettusbadu s wudem amezwer neɣ ad tettusbadu ɣer " ++"uzrir ilem." ++ ++#: data/org.gnome.mutter.gschema.xml.in:28 ++msgid "Attach modal dialogs" ++msgstr "Seddu idiwenniyen iskaranen" ++ ++#: data/org.gnome.mutter.gschema.xml.in:29 ++msgid "" ++"When true, instead of having independent titlebars, modal dialogs appear " ++"attached to the titlebar of the parent window and are moved together with " ++"the parent window." ++msgstr "" ++"Mi ara yili d tidet, deg wadeg n yifeggagen n yizwal ilelliyen, idiwenniyen " ++"iskaranen ad d-banen ddan deg ufeggag n uzwel n usfaylu maraw, ad " ++"ttwasnekzen akken akkedd usfaylu amaraw." ++ ++#: data/org.gnome.mutter.gschema.xml.in:38 ++msgid "Enable edge tiling when dropping windows on screen edges" ++msgstr "Rmed aburset n tamiwin mi ara tserseḍ isfuyla ɣef tamiwin n ugdil" ++ ++#: data/org.gnome.mutter.gschema.xml.in:39 ++msgid "" ++"If enabled, dropping windows on vertical screen edges maximizes them " ++"vertically and resizes them horizontally to cover half of the available " ++"area. Dropping windows on the top screen edge maximizes them completely." ++msgstr "" ++"Ma yella tettwarmed textiṛt-a, tarusi yisfuyla ɣef tamiwin tiratakin n ugdil " ++"ad ttwasmeɣren s wudem aglawan, akken ara ččaren azgen n temnaḍt i yellan. " ++"Azuɣer n yisfuyla ɣef tama tafellayt n ugdil ad ten-issemɣer s wudem ummid." ++ ++#: data/org.gnome.mutter.gschema.xml.in:48 ++msgid "Workspaces are managed dynamically" ++msgstr "Tallunin n umahil ttwasefrakent s wudem asmussan" ++ ++#: data/org.gnome.mutter.gschema.xml.in:49 ++msgid "" ++"Determines whether workspaces are managed dynamically or whether there’s a " ++"static number of workspaces (determined by the num-workspaces key in org." ++"gnome.desktop.wm.preferences)." ++msgstr "" ++"Yettguccul ma yella tallunin n umahil ttwasefrakent s wudem asmussan neɣ ma " ++"yella amḍan udmis n tallunin n umahil (yettwaguccel s tsarut num-workspaces " ++"deg org.gnome.desktop.wm.preferences)." ++ ++#: data/org.gnome.mutter.gschema.xml.in:58 ++msgid "Workspaces only on primary" ++msgstr "Tallunin n umahil ɣef umezwaru kan" ++ ++#: data/org.gnome.mutter.gschema.xml.in:59 ++msgid "" ++"Determines whether workspace switching should happen for windows on all " ++"monitors or only for windows on the primary monitor." ++msgstr "" ++"Yesguccul ma yella tallunin n umahil ttwasefrakent s wudem asmussan neɣ ma " ++"yella amḍan udmis n tallunin n umahil (yettwaguccel s tsarut num-workspaces " ++"deg org.gnome.desktop.wm.preferences)." ++ ++#: data/org.gnome.mutter.gschema.xml.in:67 ++msgid "Delay focus changes until the pointer stops moving" ++msgstr "Saẓey isenfal n usaḍes alamma asewwar ad iḥbes asenkez" ++ ++#: data/org.gnome.mutter.gschema.xml.in:68 ++msgid "" ++"If set to true, and the focus mode is either “sloppy” or “mouse” then the " ++"focus will not be changed immediately when entering a window, but only after " ++"the pointer stops moving." ++msgstr "" ++"Ma yella yettusbadu s tidet, askar asaḍes \"sloppy\" neɣ \"d taɣerdayt\", " ++"asiḍes ur yettwasenfal ara mi ara d-yekcem usfaylu, maca ma yeḥbes kan " ++"usewwar asenkez." ++ ++#: data/org.gnome.mutter.gschema.xml.in:78 ++msgid "Draggable border width" ++msgstr "Tehri n tama yettwazuɣaren" ++ ++#: data/org.gnome.mutter.gschema.xml.in:79 ++msgid "" ++"The amount of total draggable borders. If the theme’s visible borders are " ++"not enough, invisible borders will be added to meet this value." ++msgstr "" ++"Tasmekta tasemdayt n tamiwin i izmeren ad tettwaszuɣarent. Ma yella tamiwin " ++"ibanen n usentel d timagdazin, tamiwin ur nban ara ad ttwarnunt i wakken ad " ++"yaweḍ wazal-nni." ++ ++#: data/org.gnome.mutter.gschema.xml.in:88 ++msgid "Auto maximize nearly monitor sized windows" ++msgstr "Asemɣer awurman i yisfuyla n teɣzi i iqerben ɣer teɣzi n ugdil" ++ ++#: data/org.gnome.mutter.gschema.xml.in:89 ++msgid "" ++"If enabled, new windows that are initially the size of the monitor " ++"automatically get maximized." ++msgstr "" ++"Ma yella yettwarmed, isfuyla imaynuten i yesεan teɣzi am tin n ugdil " ++"yettnernin s wudem awurman." ++ ++#: data/org.gnome.mutter.gschema.xml.in:97 ++msgid "Place new windows in the center" ++msgstr "Sers isfuyla imaynuten deg tlemmast" ++ ++#: data/org.gnome.mutter.gschema.xml.in:98 ++msgid "" ++"When true, the new windows will always be put in the center of the active " ++"screen of the monitor." ++msgstr "" ++"Ma yella d tidet, isfuyla imaynuten zgan ttuɣalen deg tlemmast n ugdil urmid " ++"n usefrak." ++ ++#: data/org.gnome.mutter.gschema.xml.in:107 ++msgid "Enable experimental features" ++msgstr "Rmed timahilin tirmitanin" ++ ++#: data/org.gnome.mutter.gschema.xml.in:108 ++msgid "" ++"To enable experimental features, add the feature keyword to the list. " ++"Whether the feature requires restarting the compositor depends on the given " ++"feature. Any experimental feature is not required to still be available, or " ++"configurable. Don’t expect adding anything in this setting to be future " ++"proof. Currently possible keywords: • “scale-monitor-framebuffer” — makes " ++"mutter default to layout logical monitors in a logical pixel coordinate " ++"space, while scaling monitor framebuffers instead of window content, to " ++"manage HiDPI monitors. Does not require a restart. • “kms-modifiers” — makes " ++"mutter always allocate scanout buffers with explicit modifiers, if supported " ++"by the driver. Requires a restart. • “autoclose-xwayland” — automatically " ++"terminates Xwayland if all relevant X11 clients are gone. Requires a " ++"restart. • “variable-refresh-rate” — makes mutter dynamically adjust the " ++"refresh rate of the monitor when applicable if supported by the monitor, GPU " ++"and DRM driver. Configurable in Settings. Requires a restart." ++msgstr "" ++"I urmad n tmahilin tirmitanin, rnu awal n tsarut n tmahilt ɣer tebdart. " ++"Allus n usenker n usuddas yettwasra almend n tmahilt i d-yettwamudden. Ur " ++"tettwasra ara kra n tmahilt tarmitant ad teqqim tella kan neɣ tezmer ad " ++"tettuswal. Mačči yal timerna n kra deg uɣewwar-a yeskanay-d imal. Awalen n " ++"tsura yettwaqbalen akka tura: • “scale-monitor-framebuffer” — yettarra " ++"mutter ad yesεu s wudem amezwer isefraken imeẓẓulen deg wadeg ameẓẓul n " ++"yimsidag n yipiksilen, lawan n usemɣer n yikataren n usefrak deg wadeg n " ++"ugbur n usfaylu, i usefrek n HiDPI. Ur yesra allus n usenker. • “kms-" ++"modifiers” — yettarra mutter yezga yettḥerri aḥrazen n uḍummu s yisenfalayen " ++"iflayanen, ma yella yettwasefrak sɣur unuḍaf. Yesra allus n usenkar. • " ++"“autoclose-xwayland” — yettfakk s wudem awurman Xwayland ma yella akk " ++"imsaɣen X11 yesεan azal ruḥen. Yesra allus n usenker. • “variable-refresh-" ++"rate” — yettarra mutter yezmer ad isemsawi s wudem asmussan aktum n usismeḍ " ++"n usefrak, ma yella tettunefk tegnit, ma yella yettwasefrak sɣur usefrak, " ++"GPU akked unuḍaf DRM. Yettwaswal deg yiɣewwaren. Yesra alluas n usenker." ++ ++#: data/org.gnome.mutter.gschema.xml.in:144 ++msgid "Modifier to use to locate the pointer" ++msgstr "Senfel ara yettwasqedcen i usideg n usewwaṛ" ++ ++#: data/org.gnome.mutter.gschema.xml.in:145 ++msgid "This key will initiate the “locate pointer” action." ++msgstr "Tasarut-a ad twennez tigawt n \"usideg n usewwaṛ\"." ++ ++#: data/org.gnome.mutter.gschema.xml.in:152 ++msgid "Timeout for check-alive ping" ++msgstr "Tanzagt n keffu n ping n usenqed" ++ ++#: data/org.gnome.mutter.gschema.xml.in:153 ++msgid "" ++"Number of milliseconds a client has to respond to a ping request in order to " ++"not be detected as frozen. Using 0 will disable the alive check completely." ++msgstr "" ++"Amḍan n yiliten tsinin i yesεa umsaɣ i wakken ad yerr ɣef ussuter ping akken " ++"ur yettuneḥsab ara yegres. Aseqdec n 0 yesnusuy s wudem ummid asenqed n " ++"tiltin n umsaɣ." ++ ++#: data/org.gnome.mutter.gschema.xml.in:177 ++msgid "Switch monitor configurations" ++msgstr "Senfel tiwila n usefrak" ++ ++#: data/org.gnome.mutter.gschema.xml.in:182 ++msgid "Rotates the built-in monitor configuration" ++msgstr "Sezzi tawila n usefrak usliɣ" ++ ++#: data/org.gnome.mutter.gschema.xml.in:187 ++msgid "Cancel any active input capture session" ++msgstr "Sefsex akk n tɣimiyin n tuṭṭfa n unekcum yettwaremden" ++ ++#: data/org.gnome.mutter.wayland.gschema.xml.in:12 ++msgid "Switch to VT 1" ++msgstr "Ddu ɣer VT 1" ++ ++#: data/org.gnome.mutter.wayland.gschema.xml.in:16 ++msgid "Switch to VT 2" ++msgstr "Ddu ɣer VT 2" ++ ++#: data/org.gnome.mutter.wayland.gschema.xml.in:20 ++msgid "Switch to VT 3" ++msgstr "Ddu ɣer VT 3" ++ ++#: data/org.gnome.mutter.wayland.gschema.xml.in:24 ++msgid "Switch to VT 4" ++msgstr "Ddu ɣer VT 4" ++ ++#: data/org.gnome.mutter.wayland.gschema.xml.in:28 ++msgid "Switch to VT 5" ++msgstr "Ddu ɣer VT 5" ++ ++#: data/org.gnome.mutter.wayland.gschema.xml.in:32 ++msgid "Switch to VT 6" ++msgstr "Ddu ɣer VT 6" ++ ++#: data/org.gnome.mutter.wayland.gschema.xml.in:36 ++msgid "Switch to VT 7" ++msgstr "Ddu ɣer VT 7" ++ ++#: data/org.gnome.mutter.wayland.gschema.xml.in:40 ++msgid "Switch to VT 8" ++msgstr "Ddu ɣer VT 8" ++ ++#: data/org.gnome.mutter.wayland.gschema.xml.in:44 ++msgid "Switch to VT 9" ++msgstr "Ddu ɣer VT 9" ++ ++#: data/org.gnome.mutter.wayland.gschema.xml.in:48 ++msgid "Switch to VT 10" ++msgstr "Ddu ɣer VT 10" ++ ++#: data/org.gnome.mutter.wayland.gschema.xml.in:52 ++msgid "Switch to VT 11" ++msgstr "Ddu ɣer VT 11" ++ ++#: data/org.gnome.mutter.wayland.gschema.xml.in:56 ++msgid "Switch to VT 12" ++msgstr "Ddu ɣer VT 12" ++ ++#: data/org.gnome.mutter.wayland.gschema.xml.in:60 ++msgid "Re-enable shortcuts" ++msgstr "Ales armad n yinegzumen n unasiw" ++ ++#: data/org.gnome.mutter.wayland.gschema.xml.in:70 ++msgid "Allow X11 grabs to lock keyboard focus with Xwayland" ++msgstr "Sireg tuṭṭfiwin X11 ad sekkṛent afukus n unasiw s Xwayland" ++ ++#: data/org.gnome.mutter.wayland.gschema.xml.in:71 ++msgid "" ++"Allow all keyboard events to be routed to X11 “override redirect” windows " ++"with a grab when running in Xwayland. This option is to support X11 clients " ++"which map an “override redirect” window (which do not receive keyboard " ++"focus) and issue a keyboard grab to force all keyboard events to that " ++"window. This option is seldom used and has no effect on regular X11 windows " ++"which can receive keyboard focus under normal circumstances. For a X11 grab " ++"to be taken into account under Wayland, the client must also either send a " ++"specific X11 ClientMessage to the root window or be among the applications " ++"allowed in key “xwayland-grab-access-rules”." ++msgstr "" ++"Asireg n usezzi n meṛṛa tidyanin n unasiw ɣer yisfuyla X11 \"override " ++"redirect\" s uzuɣer lawan n uselkem deg Xwayland. Taxtiṛt-a tettaǧǧa imsaɣen " ++"X11 azuɣer n usfaylu \"override redirect\" (ur nremmes ara asiḍes n unasiw) " ++"akked useqdec n uzuɣer i uḥettem n meṛṛa tidyanin n unasiw ɣer usfaylu-a. " ++"Taxtiṛt-a ur tettwaseqdac ara s waṭas, ur txeddem kra i yisfuyla X11 i " ++"izemren ad remsen asiḍes n unasiw deg tegnatin timugna. I uzuɣer n X11 ad " ++"tettwaṭṭef deg Wayland, amsaɣ ilaq ad yazen daɣen X11 ClientMessage ɣer " ++"usfaylu aẓar neɣ ad yili seg yisnasen yettwasirgen deg tsarut “xwayland-grab-" ++"access-rules”." ++ ++#: data/org.gnome.mutter.wayland.gschema.xml.in:90 ++msgid "Xwayland applications allowed to issue keyboard grabs" ++msgstr "Isnasen Xwayland ttusirgent ad gent tuṭṭfiwin n unasiw" ++ ++#: data/org.gnome.mutter.wayland.gschema.xml.in:91 ++msgid "" ++"List the resource names or resource class of X11 windows either allowed or " ++"not allowed to issue X11 keyboard grabs under Xwayland. The resource name or " ++"resource class of a given X11 window can be obtained using the command " ++"“xprop WM_CLASS”. Wildcards “*” and jokers “?” in the values are supported. " ++"Values starting with “!” are denied, which has precedence over the list of " ++"values allowed, to revoke applications from the default system list. The " ++"default system list includes the following applications: " ++"“@XWAYLAND_GRAB_DEFAULT_ACCESS_RULES@” Users can break an existing grab by " ++"using the specific keyboard shortcut defined by the keybinding key “restore-" ++"shortcuts”." ++msgstr "" ++"Bder ismawen n yiɣbula neɣ ismilen n yiɣbula n yisfuyla X11 yettwasirgen neɣ " ++"uhu, ad gen tuṭṭfiwin n unasiw X11 deg Wayland. Isem neɣ asmel n uɣbalu n " ++"usfaylu X11 i yettwamudden zemren ad d-ttwawin s useqdec n tladna “xprop " ++"WM_CLASS”. Isekkilen imcettlen “*” akked ijukiṛen “?” deg wazalen " ++"yettwasferken. Azalen i ibeddun s \"!\" ttwagin, i yellan deg tebdart " ++"yettwasirgen yakan, i usefsex n yisnasen seg tebdart n unagraw amezwer. " ++"Tabdart n unagraw amezwer deg-s isnasen-a: " ++"“@XWAYLAND_GRAB_DEFAULT_ACCESS_RULES@” Iseqdacen zemren ad sḥebsen tira " ++"yellan yakan s useqdec n unegzum n unasiw uzzig yettusbadun s tsarut n " ++"tuqqna \"restore-shortcuts\"." ++ ++#: data/org.gnome.mutter.wayland.gschema.xml.in:116 ++msgid "Disable selected X extensions in Xwayland" ++msgstr "Sens isiɣzaf X i yettwafernen deg Xwayland" ++ ++#: data/org.gnome.mutter.wayland.gschema.xml.in:117 ++msgid "" ++"This option disables the selected X extensions in Xwayland if Xwayland was " ++"built with support for those X extensions. This option has no effect if " ++"Xwayland was built without support for the selected extensions. Xwayland " ++"needs to be restarted for this setting to take effect." ++msgstr "" ++"Taxtiṛt-a tesnusuy isiɣzaf X i yettwafernen deg Xwayland ma yella Xwayland " ++"yebna s usefrek i yisiɣzaf-a X. Taxtiṛt-a ur d-tgellu s wecemma ma yella " ++"Xwayland yebna war asefrek n yisiɣzaf i yettwafernen. Xwayland yesra ad " ++"yales asenker i uɣewwar-a i wakken ad yeddu." ++ ++#: data/org.gnome.mutter.wayland.gschema.xml.in:130 ++msgid "Allow X11 clients with a different endianness to connect to Xwayland" ++msgstr "Sireg imsaɣen X11 s at taggara yemgaraden i tuqqna ɣer Xwayland" ++ ++#: data/org.gnome.mutter.wayland.gschema.xml.in:131 ++msgid "" ++"Allow connections from clients with an endianness different to that of " ++"Xwayland. The X server byte-swapping code is a huge attack surface, much of " ++"that code in Xwayland is prone to security issues. The use-case of byte-" ++"swapped clients is very niche, and disabled by default in Xwayland. Enable " ++"this option to instruct Xwayland to accept connections from X11 clients with " ++"a different endianness. This option has no effect if Xwayland does not " ++"support the command line option +byteswappedclients/-byteswappedclients to " ++"control that setting. Xwayland needs to be restarted for this setting to " ++"take effect." ++msgstr "" ++"Asireg n tuqqniwin seg yimsaɣen ideg endianness yemgarad ɣef win n Xwayland. " ++"Tangalt n umyibedel n yibiten n uqeddac X d tajumma meqqren n uzḍam, amur " ++"ameqqran seg tengalt-a deg Xwayland yezmer ad yesεu uguren n tɣellist. " ++"Addaden n useqdec n yimsaɣen yettmyibeddalen ibiten drus maḍi, yettwasens s " ++"wudem amezwer deg Xwayland. Armad n textiṛt-a i ussuter deg Xwayland aqbal n " ++"tuqqniwin n yimsaɣen X11 s endianness yemgaraden. Taxtiṛt-a ur txeddem " ++"acemma ma yella Xwayland ur yessefrak ara taxtiṛt n yizirig n tiludna " ++"+byteswappedclients/-byteswappedclients i usenqed n uɣewwar-a. Xwayland " ++"yesra ad yales asenker i uɣewwar-a i wakken ad yeṭṭef." ++ ++#: src/backends/meta-monitor.c:251 ++msgid "Built-in display" ++msgstr "Agdil usliɣ" ++ ++#: src/backends/meta-monitor.c:278 ++msgid "Unknown" ++msgstr "Arussin" ++ ++#: src/backends/meta-monitor.c:280 ++msgid "Unknown Display" ++msgstr "Agdil arussin" ++ ++#: src/backends/meta-monitor.c:288 ++#, c-format ++msgctxt "" ++"This is a monitor vendor name, followed by a size in inches, like 'Dell 15\"'" ++msgid "%s %s" ++msgstr "%s %s" ++ ++#: src/backends/meta-monitor.c:296 ++#, c-format ++msgctxt "" ++"This is a monitor vendor name followed by product/model name where size in " ++"inches could not be calculated, e.g. Dell U2414H" ++msgid "%s %s" ++msgstr "%s %s" ++ ++#: src/core/bell.c:193 ++msgid "Bell event" ++msgstr "Tadyant n unayna" ++ ++#: src/core/display.c:734 ++msgid "Privacy Screen Enabled" ++msgstr "Agdil n tbaḍnit yermed" ++ ++#: src/core/display.c:735 ++msgid "Privacy Screen Disabled" ++msgstr "Agdil n tbaḍnit yensa" ++ ++#: src/core/meta-context-main.c:601 ++msgid "Replace the running window manager" ++msgstr "Semselsi amsefrak n usfaylu yettwaselkamen" ++ ++#: src/core/meta-context-main.c:607 ++msgid "X Display to use" ++msgstr "Taskant X ara yettusqedcen" ++ ++#: src/core/meta-context-main.c:613 ++msgid "Disable connection to session manager" ++msgstr "Sens tuqqna i umsefrak n tɣimit" ++ ++#: src/core/meta-context-main.c:619 ++msgid "Specify session management ID" ++msgstr "Asulay n usefrek n tɣimit tuzzigt" ++ ++#: src/core/meta-context-main.c:625 ++msgid "Initialize session from savefile" ++msgstr "Wennez tiɣimit seg ufaylu i yettwaskelsen" ++ ++#: src/core/meta-context-main.c:631 ++msgid "Make X calls synchronous" ++msgstr "Err isawalen X mtawan" ++ ++#: src/core/meta-context-main.c:639 ++msgid "Run as a wayland compositor" ++msgstr "Selkem am umsuddas Wayland" ++ ++#: src/core/meta-context-main.c:645 ++msgid "Run as a nested compositor" ++msgstr "Selkem am umsuddas yemyekcamen" ++ ++#: src/core/meta-context-main.c:651 ++msgid "Run wayland compositor without starting Xwayland" ++msgstr "Selkem amsuddas wayland war asenker n Xwayland" ++ ++#: src/core/meta-context-main.c:657 ++msgid "Specify Wayland display name to use" ++msgstr "Sded agdil Wayland i useqdec" ++ ++#: src/core/meta-context-main.c:665 ++msgid "Run as a full display server, rather than nested" ++msgstr "Selkem am uqeddac n uskan aččuran, deg wadeg n umyikcam" ++ ++#: src/core/meta-context-main.c:670 ++msgid "Run as a headless display server" ++msgstr "Selkem am uqeddac n uskan war aqerru" ++ ++#: src/core/meta-context-main.c:675 ++msgid "Add persistent virtual monitor (WxH or WxH@R)" ++msgstr "Rnu asefrak uhlis amaɣlal (WxH neɣ WxH@R)" ++ ++#: src/core/meta-context-main.c:687 ++msgid "Run with X11 backend" ++msgstr "Selkem s ugdil n deffir X11" ++ ++#: src/core/meta-context-main.c:693 ++msgid "Profile performance using trace instrumentation" ++msgstr "Timellit n umaɣnu s useqdec n ifecka n uḍfar" ++ ++#: src/core/meta-context-main.c:699 ++msgid "Enable debug control D-Bus interface" ++msgstr "Rmed agrudem D-Bus n usenqed n tseɣtayt" ++ ++#. TRANSLATORS: This string refers to a button that switches between ++#. * different modes. ++#. ++#: src/core/meta-pad-action-mapper.c:826 ++#, c-format ++msgid "Mode Switch (Group %d)" ++msgstr "Asenfel n uskar (Agraw %d)" ++ ++#. TRANSLATORS: This string refers to an action, cycles drawing tablets' ++#. * mapping through the available outputs. ++#. ++#: src/core/meta-pad-action-mapper.c:848 ++msgid "Switch monitor" ++msgstr "Senfel asefrak" ++ ++#: src/core/meta-pad-action-mapper.c:850 ++msgid "Show on-screen help" ++msgstr "Sken tallalt ɣef ugdil" ++ ++#. Translators: this string will appear in Sysprof ++#: src/core/meta-profiler.c:109 src/core/meta-profiler.c:299 ++msgid "Compositor" ++msgstr "Amsuddas" ++ ++#: src/core/mutter.c:74 ++msgid "Print version" ++msgstr "Sken lqem" ++ ++#: src/core/mutter.c:80 ++msgid "Mutter plugin to use" ++msgstr "Azegrir Mutter ara yettwasqedcen" ++ ++#: src/core/prefs.c:1842 ++#, c-format ++msgid "Workspace %d" ++msgstr "Tallunt n umahil %d" ++ ++#: src/core/util.c:139 ++msgid "Mutter was compiled without support for verbose mode" ++msgstr "Mutter yettwasefsu war asefrek n uskar ɣezzifen" ++ ++#: src/core/workspace.c:510 ++msgid "Workspace switched" ++msgstr "Tallunt n umahil tettwasenfel" ++ ++#: src/wayland/meta-wayland-tablet-pad.c:532 ++#, c-format ++msgid "Mode Switch: Mode %d" ++msgstr "Asenfel n uskar: Askar %d" ++ ++#: src/x11/meta-x11-display.c:723 ++#, c-format ++msgid "" ++"Display “%s” already has a window manager; try using the --replace option to " ++"replace the current window manager." ++msgstr "" ++"Sken \"%s\" yesεa yakan asefrak n usfaylu; εreḍ seqdec taxtiṛt --replace i " ++"usemselsi n usefrak n yisfuyla amiran." ++ ++#: src/x11/meta-x11-display.c:1088 ++#, c-format ++msgid "Failed to open X Window System display “%s”" ++msgstr "Yecceḍ ulday n uskan n n yifuyla X Window \"%s\"" ++ ++#: src/x11/meta-x11-display.c:1268 ++#, c-format ++msgid "Screen %d on display “%s” is invalid" ++msgstr "Agdil %d ɣef uskan \"%s\" d arameɣtu" ++ ++#. This probably means that a non-WM compositor like xcompmgr is running; ++#. * we have no way to get it to exit ++#: src/x11/meta-x11-display.c:2547 ++#, c-format ++msgid "" ++"Another compositing manager is already running on screen %i on display “%s”." ++msgstr "" ++"Asefrak niḍen n usuddes yella yakan yettwaselkam ɣef ugdil %i ɣef uskan " ++"\"%s\"." ++ ++#: src/x11/meta-x11-selection-input-stream.c:475 ++#, c-format ++msgid "Format %s not supported" ++msgstr "Amasal %s ur yettwasefrak ara" ++ ++#: src/x11/window-props.c:528 ++#, c-format ++msgid "%s (on %s)" ++msgstr "%s (ɣef %s)" +diff --git a/po/ko.po b/po/ko.po +index 558c6c5ca..3ac430f08 100644 +--- a/po/ko.po ++++ b/po/ko.po +@@ -7,7 +7,7 @@ + # + # Updated in mutter: + # Changwoo Ryu , 2011-2017. +-# Gwan-gyeong Mun , 2018-2023. ++# Gwan-gyeong Mun , 2018-2024. + # + # + # 주의: +@@ -17,8 +17,8 @@ + msgid "" + msgstr "" + "Project-Id-Version: mutter\n" +-"Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/mutter/issues\n" +-"POT-Creation-Date: 2023-08-23 16:49+0000\n" ++"Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/mutter/issues/\n" ++"POT-Creation-Date: 2024-03-11 11:31+0000\n" + "PO-Revision-Date: 2023-03-15 18:43+0200\n" + "Last-Translator: Gwan-gyeong Mun \n" + "Language-Team: GNOME Korea \n" +@@ -257,11 +257,11 @@ msgstr "창을 세로 방향으로 최대화" + msgid "Maximize window horizontally" + msgstr "창을 가로 방향으로 최대화" + +-#: data/50-mutter-windows.xml:41 data/org.gnome.mutter.gschema.xml.in:164 ++#: data/50-mutter-windows.xml:41 data/org.gnome.mutter.gschema.xml.in:167 + msgid "View split on left" + msgstr "왼쪽 나눔창 보기" + +-#: data/50-mutter-windows.xml:45 data/org.gnome.mutter.gschema.xml.in:169 ++#: data/50-mutter-windows.xml:45 data/org.gnome.mutter.gschema.xml.in:172 + msgid "View split on right" + msgstr "오른쪽 나눔창 보기" + +@@ -394,10 +394,11 @@ msgid "" + "space, while scaling monitor framebuffers instead of window content, to " + "manage HiDPI monitors. Does not require a restart. • “kms-modifiers” — makes " + "mutter always allocate scanout buffers with explicit modifiers, if supported " +-"by the driver. Requires a restart. • “rt-scheduler” — makes mutter request a " +-"low priority real-time scheduling. Requires a restart. • “autoclose-" +-"xwayland” — automatically terminates Xwayland if all relevant X11 clients " +-"are gone. Requires a restart." ++"by the driver. Requires a restart. • “autoclose-xwayland” — automatically " ++"terminates Xwayland if all relevant X11 clients are gone. Requires a " ++"restart. • “variable-refresh-rate” — makes mutter dynamically adjust the " ++"refresh rate of the monitor when applicable if supported by the monitor, GPU " ++"and DRM driver. Configurable in Settings. Requires a restart." + msgstr "" + "실험적인 기능을 사용하려면, 해당 기능 키워드를 목록에 추가하십시오. 기능에 따" + "라 컴포지터를 다시 시작해야 할 수도 있습니다. 실험적인 기능은 사용할 수 없을 " +@@ -407,24 +408,25 @@ msgstr "" + "를 배치하고, HiDPI 모니터를 관리하기 위해 창 내용 대신 모니터 프레임 버퍼를 " + "스케일링합니다. 재시작은 필요하지 않습니다. • “kms-modifiers” — 드라이버에서 " + "지원하는 경우 머터가 항상 명시적 한정자를 사용하여 스캔아웃 버퍼를 할당 합니" +-"다. 재시작이 필요합니다. • “rt-scheduler” — 머터 요청을 우선순위가 낮은 실시" +-"간 스케줄링으로 만듭니다. 재시작이 필요합니다. • “autoclose-xwayland” — 모든 " +-"X11 관련 클라이언트가 종료되면 X웨일랜드를 자동으로 종료합니다. 재시작이 필요" +-"하지 않습니다." ++"다. 재시작이 필요합니다. • “autoclose-xwayland” — 모든 X11 관련 클라이언트가 " ++"종료되면 X웨일랜드를 자동으로 종료합니다. 재시작이 필요하지 않습니다. • " ++"“variable-refresh-rate” — 모니터, GPU 및 DRM 드라이버에서 지원하는 경우 모니" ++"터의 새로 고침 빈도를 동적으로 조정합니다. 설정에서 구성 가능하며, 재시작이 " ++"필요합니다." + +-#: data/org.gnome.mutter.gschema.xml.in:141 ++#: data/org.gnome.mutter.gschema.xml.in:144 + msgid "Modifier to use to locate the pointer" + msgstr "포인터 위치 찾기에 사용할 키" + +-#: data/org.gnome.mutter.gschema.xml.in:142 ++#: data/org.gnome.mutter.gschema.xml.in:145 + msgid "This key will initiate the “locate pointer” action." + msgstr "이 키는 “포인터 위치 찾기” 동작을 합니다." + +-#: data/org.gnome.mutter.gschema.xml.in:149 ++#: data/org.gnome.mutter.gschema.xml.in:152 + msgid "Timeout for check-alive ping" + msgstr "활성 확인 핑 시간 초과" + +-#: data/org.gnome.mutter.gschema.xml.in:150 ++#: data/org.gnome.mutter.gschema.xml.in:153 + msgid "" + "Number of milliseconds a client has to respond to a ping request in order to " + "not be detected as frozen. Using 0 will disable the alive check completely." +@@ -432,15 +434,15 @@ msgstr "" + "중지된 것으로 감지되지 않도록 클라이언트가 핑 요청에 응답해야 하는 시간 (밀리" + "초). 0을 사용하면 활성 확인이 완전히 비활성화됩니다." + +-#: data/org.gnome.mutter.gschema.xml.in:174 ++#: data/org.gnome.mutter.gschema.xml.in:177 + msgid "Switch monitor configurations" + msgstr "모니터 설정 전환" + +-#: data/org.gnome.mutter.gschema.xml.in:179 ++#: data/org.gnome.mutter.gschema.xml.in:182 + msgid "Rotates the built-in monitor configuration" + msgstr "내장된 모니터 설정을 돌아가면서 전환합니다" + +-#: data/org.gnome.mutter.gschema.xml.in:184 ++#: data/org.gnome.mutter.gschema.xml.in:187 + msgid "Cancel any active input capture session" + msgstr "활성화된 입력 캡처 세션 취소" + +@@ -587,26 +589,26 @@ msgstr "" + "byteswappedclients을 지원하지 않는 경우 효과가 없습니다. 이 설정을 적용하려" + "면 X웨일랜드를 다시 시작해야 합니다." + +-#: src/backends/meta-monitor.c:253 ++#: src/backends/meta-monitor.c:251 + msgid "Built-in display" + msgstr "내장 디스플레이" + +-#: src/backends/meta-monitor.c:280 ++#: src/backends/meta-monitor.c:278 + msgid "Unknown" + msgstr "알 수 없음" + +-#: src/backends/meta-monitor.c:282 ++#: src/backends/meta-monitor.c:280 + msgid "Unknown Display" + msgstr "알 수 없는 디스플레이" + +-#: src/backends/meta-monitor.c:290 ++#: src/backends/meta-monitor.c:288 + #, c-format + msgctxt "" + "This is a monitor vendor name, followed by a size in inches, like 'Dell 15\"'" + msgid "%s %s" + msgstr "%s %s" + +-#: src/backends/meta-monitor.c:298 ++#: src/backends/meta-monitor.c:296 + #, c-format + msgctxt "" + "This is a monitor vendor name followed by product/model name where size in " +@@ -618,78 +620,82 @@ msgstr "%s %s" + msgid "Bell event" + msgstr "삑소리 이벤트" + +-#: src/core/display.c:723 ++#: src/core/display.c:734 + msgid "Privacy Screen Enabled" + msgstr "사생활 보호 화면 사용" + +-#: src/core/display.c:724 ++#: src/core/display.c:735 + msgid "Privacy Screen Disabled" + msgstr "사생활 보호 화면 해제" + +-#: src/core/meta-context-main.c:581 ++#: src/core/meta-context-main.c:601 + msgid "Replace the running window manager" + msgstr "실행 중인 창 관리자를 바꿉니다" + +-#: src/core/meta-context-main.c:587 ++#: src/core/meta-context-main.c:607 + msgid "X Display to use" + msgstr "사용할 X 디스플레이" + +-#: src/core/meta-context-main.c:593 ++#: src/core/meta-context-main.c:613 + msgid "Disable connection to session manager" + msgstr "세션 관리자와 연결 하지 않습니다" + +-#: src/core/meta-context-main.c:599 ++#: src/core/meta-context-main.c:619 + msgid "Specify session management ID" + msgstr "세션 관리 ID를 지정합니다" + +-#: src/core/meta-context-main.c:605 ++#: src/core/meta-context-main.c:625 + msgid "Initialize session from savefile" + msgstr "저장 파일에서 세션을 초기화 합니다" + +-#: src/core/meta-context-main.c:611 ++#: src/core/meta-context-main.c:631 + msgid "Make X calls synchronous" + msgstr "동기 X 호출을 합니다" + +-#: src/core/meta-context-main.c:619 ++#: src/core/meta-context-main.c:639 + msgid "Run as a wayland compositor" + msgstr "웨일랜드 컴포지터로 실행합니다" + +-#: src/core/meta-context-main.c:625 ++#: src/core/meta-context-main.c:645 + msgid "Run as a nested compositor" + msgstr "중첩 컴포지터로 실행합니다" + +-#: src/core/meta-context-main.c:631 ++#: src/core/meta-context-main.c:651 + msgid "Run wayland compositor without starting Xwayland" + msgstr "웨일랜드 컴포지터를 X웨일랜드 시작없이 실행합니다" + +-#: src/core/meta-context-main.c:637 ++#: src/core/meta-context-main.c:657 + msgid "Specify Wayland display name to use" + msgstr "사용할 웨일랜드 디스플레이 이름 지정" + +-#: src/core/meta-context-main.c:645 ++#: src/core/meta-context-main.c:665 + msgid "Run as a full display server, rather than nested" + msgstr "전체 디스플레이 서버로 실행, 중첩 컴포지터가 아님" + +-#: src/core/meta-context-main.c:650 ++#: src/core/meta-context-main.c:670 + msgid "Run as a headless display server" + msgstr "헤드리스 디스플레이 서버로 실행" + +-#: src/core/meta-context-main.c:655 ++#: src/core/meta-context-main.c:675 + msgid "Add persistent virtual monitor (WxH or WxH@R)" + msgstr "영구적인 가상 모니터 추가 (WxH or WxH@R)" + +-#: src/core/meta-context-main.c:667 ++#: src/core/meta-context-main.c:687 + msgid "Run with X11 backend" + msgstr "X11 백 엔드로 실행 합니다" + +-#: src/core/meta-context-main.c:673 ++#: src/core/meta-context-main.c:693 + msgid "Profile performance using trace instrumentation" + msgstr "추적 계측을 사용한 성능 프로파일" + ++#: src/core/meta-context-main.c:699 ++msgid "Enable debug control D-Bus interface" ++msgstr "디버깅 제어 D-Bus 인터페이스 사용" ++ + #. TRANSLATORS: This string refers to a button that switches between + #. * different modes. + #. +-#: src/core/meta-pad-action-mapper.c:807 ++#: src/core/meta-pad-action-mapper.c:826 + #, c-format + msgid "Mode Switch (Group %d)" + msgstr "모드 전환 (그룹 %d)" +@@ -697,16 +703,16 @@ msgstr "모드 전환 (그룹 %d)" + #. TRANSLATORS: This string refers to an action, cycles drawing tablets' + #. * mapping through the available outputs. + #. +-#: src/core/meta-pad-action-mapper.c:829 ++#: src/core/meta-pad-action-mapper.c:848 + msgid "Switch monitor" + msgstr "모니터 전환" + +-#: src/core/meta-pad-action-mapper.c:831 ++#: src/core/meta-pad-action-mapper.c:850 + msgid "Show on-screen help" + msgstr "화면 도움말 표시" + + #. Translators: this string will appear in Sysprof +-#: src/core/meta-profiler.c:111 src/core/meta-profiler.c:301 ++#: src/core/meta-profiler.c:109 src/core/meta-profiler.c:299 + msgid "Compositor" + msgstr "컴포지터" + +@@ -718,7 +724,7 @@ msgstr "버전을 출력합니다" + msgid "Mutter plugin to use" + msgstr "사용할 머터 플러그인" + +-#: src/core/prefs.c:1843 ++#: src/core/prefs.c:1842 + #, c-format + msgid "Workspace %d" + msgstr "작업 공간 %d" +@@ -727,16 +733,16 @@ msgstr "작업 공간 %d" + msgid "Mutter was compiled without support for verbose mode" + msgstr "머터가 자세한 정보 표시 모드를 지원하지 않게 컴파일되었습니다" + +-#: src/core/workspace.c:535 ++#: src/core/workspace.c:510 + msgid "Workspace switched" + msgstr "작업 공간 전환" + +-#: src/wayland/meta-wayland-tablet-pad.c:534 ++#: src/wayland/meta-wayland-tablet-pad.c:532 + #, c-format + msgid "Mode Switch: Mode %d" + msgstr "모드 전환: 모드 %d" + +-#: src/x11/meta-x11-display.c:708 ++#: src/x11/meta-x11-display.c:723 + #, c-format + msgid "" + "Display “%s” already has a window manager; try using the --replace option to " +@@ -745,19 +751,19 @@ msgstr "" + "디스플레이 “%s”에 이미 창 관리자가 있습니다. 현재 창 관리자를 바꾸려면 --" + "replace 옵션을 써보십시오." + +-#: src/x11/meta-x11-display.c:1073 ++#: src/x11/meta-x11-display.c:1088 + #, c-format + msgid "Failed to open X Window System display “%s”" + msgstr "X 윈도 시스템 디스플레이 “%s”을(를) 여는데 실패하였습니다" + +-#: src/x11/meta-x11-display.c:1219 ++#: src/x11/meta-x11-display.c:1268 + #, c-format + msgid "Screen %d on display “%s” is invalid" + msgstr "디스플레이 “%2$s”의 화면 %1$d은(는) 잘못되었습니다" + + #. This probably means that a non-WM compositor like xcompmgr is running; + #. * we have no way to get it to exit +-#: src/x11/meta-x11-display.c:2540 ++#: src/x11/meta-x11-display.c:2547 + #, c-format + msgid "" + "Another compositing manager is already running on screen %i on display “%s”." +@@ -771,7 +777,7 @@ msgid "Format %s not supported" + msgstr "%s 형식은 지원하지 않습니다" + + # <창제목> (on <기계>) +-#: src/x11/window-props.c:548 ++#: src/x11/window-props.c:528 + #, c-format + msgid "%s (on %s)" + msgstr "%s (%s에서)" +diff --git a/po/nl.po b/po/nl.po +index 1aae5aca9..9f4d33e05 100644 +--- a/po/nl.po ++++ b/po/nl.po +@@ -10,8 +10,8 @@ msgid "" + msgstr "" + "Project-Id-Version: mutter\n" + "Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/mutter/issues/\n" +-"POT-Creation-Date: 2024-02-12 13:12+0000\n" +-"PO-Revision-Date: 2024-02-18 17:09+0100\n" ++"POT-Creation-Date: 2024-03-16 16:36+0000\n" ++"PO-Revision-Date: 2024-03-31 12:12+0200\n" + "Last-Translator: Nathan Follens \n" + "Language-Team: GNOME-NL https://matrix.to/#/#nl:gnome.org\n" + "Language: nl\n" +@@ -250,19 +250,19 @@ msgstr "Venster verticaal maximaliseren" + msgid "Maximize window horizontally" + msgstr "Venster horizontaal maximaliseren" + +-#: data/50-mutter-windows.xml:41 data/org.gnome.mutter.gschema.xml.in:160 ++#: data/50-mutter-windows.xml:41 data/org.gnome.mutter.gschema.xml.in:167 + msgid "View split on left" + msgstr "Weergave gesplitst op links" + +-#: data/50-mutter-windows.xml:45 data/org.gnome.mutter.gschema.xml.in:165 ++#: data/50-mutter-windows.xml:45 data/org.gnome.mutter.gschema.xml.in:172 + msgid "View split on right" + msgstr "Weergave gesplitst op rechts" + +-#: data/org.gnome.mutter.gschema.xml.in:14 ++#: data/org.gnome.mutter.gschema.xml.in:15 + msgid "Modifier to use for extended window management operations" + msgstr "Controletoets voor uitgebreide vensterbeheerfunctionaliteit" + +-#: data/org.gnome.mutter.gschema.xml.in:15 ++#: data/org.gnome.mutter.gschema.xml.in:16 + msgid "" + "This key will initiate the “overlay”, which is a combination window overview " + "and application launching system. The default is intended to be the “Windows " +@@ -274,11 +274,11 @@ msgstr "" + "toets’ en de bedoeling is dat deze sneltoets ofwel de standaardwaarde bevat " + "ofwel leeggemaakt is." + +-#: data/org.gnome.mutter.gschema.xml.in:27 ++#: data/org.gnome.mutter.gschema.xml.in:28 + msgid "Attach modal dialogs" + msgstr "Blokkerende dialoogvensters vastmaken" + +-#: data/org.gnome.mutter.gschema.xml.in:28 ++#: data/org.gnome.mutter.gschema.xml.in:29 + msgid "" + "When true, instead of having independent titlebars, modal dialogs appear " + "attached to the titlebar of the parent window and are moved together with " +@@ -288,11 +288,11 @@ msgstr "" + "maar vastgekoppeld aan het achterliggende venster en bewegen daardoor ook " + "mee met het achterliggende venster." + +-#: data/org.gnome.mutter.gschema.xml.in:37 ++#: data/org.gnome.mutter.gschema.xml.in:38 + msgid "Enable edge tiling when dropping windows on screen edges" + msgstr "Randtegels inschakelen bij het slepen van vensters naar schermranden" + +-#: data/org.gnome.mutter.gschema.xml.in:38 ++#: data/org.gnome.mutter.gschema.xml.in:39 + msgid "" + "If enabled, dropping windows on vertical screen edges maximizes them " + "vertically and resizes them horizontally to cover half of the available " +@@ -303,11 +303,11 @@ msgstr "" + "schermgrootte brengen. Het laten vallen van vensters op de bovenrand van het " + "scherm maximaliseert ze helemaal." + +-#: data/org.gnome.mutter.gschema.xml.in:47 ++#: data/org.gnome.mutter.gschema.xml.in:48 + msgid "Workspaces are managed dynamically" + msgstr "Werkbladen worden dynamisch beheerd" + +-#: data/org.gnome.mutter.gschema.xml.in:48 ++#: data/org.gnome.mutter.gschema.xml.in:49 + msgid "" + "Determines whether workspaces are managed dynamically or whether there’s a " + "static number of workspaces (determined by the num-workspaces key in org." +@@ -317,11 +317,11 @@ msgstr "" + "werkbladen is (gegeven door de sleutel num-workspaces in org.gnome.desktop." + "wm.preferences)." + +-#: data/org.gnome.mutter.gschema.xml.in:57 ++#: data/org.gnome.mutter.gschema.xml.in:58 + msgid "Workspaces only on primary" + msgstr "Werkbladen alleen voor primaire venster" + +-#: data/org.gnome.mutter.gschema.xml.in:58 ++#: data/org.gnome.mutter.gschema.xml.in:59 + msgid "" + "Determines whether workspace switching should happen for windows on all " + "monitors or only for windows on the primary monitor." +@@ -329,11 +329,11 @@ msgstr "" + "Geeft aan of wisselen van werkblad voor vensters op alle schermen of alleen " + "voor vensters op het hoofdscherm moet gebeuren." + +-#: data/org.gnome.mutter.gschema.xml.in:66 ++#: data/org.gnome.mutter.gschema.xml.in:67 + msgid "Delay focus changes until the pointer stops moving" + msgstr "Aandacht vertragen totdat de muispijl stopt met bewegen" + +-#: data/org.gnome.mutter.gschema.xml.in:67 ++#: data/org.gnome.mutter.gschema.xml.in:68 + msgid "" + "If set to true, and the focus mode is either “sloppy” or “mouse” then the " + "focus will not be changed immediately when entering a window, but only after " +@@ -343,11 +343,11 @@ msgstr "" + "‘mouse’, dan zal de aandacht niet direct veranderd worden bij het binnengaan " + "van een venster, maar slechts wanneer de muispijl stopt met bewegen." + +-#: data/org.gnome.mutter.gschema.xml.in:77 ++#: data/org.gnome.mutter.gschema.xml.in:78 + msgid "Draggable border width" + msgstr "Sleepbare randbreedte" + +-#: data/org.gnome.mutter.gschema.xml.in:78 ++#: data/org.gnome.mutter.gschema.xml.in:79 + msgid "" + "The amount of total draggable borders. If the theme’s visible borders are " + "not enough, invisible borders will be added to meet this value." +@@ -356,11 +356,11 @@ msgstr "" + "onvoldoende zijn, worden onzichtbare randen toegevoegd om deze waarde te " + "bereiken." + +-#: data/org.gnome.mutter.gschema.xml.in:87 ++#: data/org.gnome.mutter.gschema.xml.in:88 + msgid "Auto maximize nearly monitor sized windows" + msgstr "Vensters van bijna-monitorformaat auto-maximaliseren" + +-#: data/org.gnome.mutter.gschema.xml.in:88 ++#: data/org.gnome.mutter.gschema.xml.in:89 + msgid "" + "If enabled, new windows that are initially the size of the monitor " + "automatically get maximized." +@@ -368,11 +368,11 @@ msgstr "" + "Indien ingeschakeld, worden vensters die initieel ongeveer even groot zijn " + "als de monitor automatisch gemaximaliseerd." + +-#: data/org.gnome.mutter.gschema.xml.in:96 ++#: data/org.gnome.mutter.gschema.xml.in:97 + msgid "Place new windows in the center" + msgstr "Plaats nieuwe vensters in het midden" + +-#: data/org.gnome.mutter.gschema.xml.in:97 ++#: data/org.gnome.mutter.gschema.xml.in:98 + msgid "" + "When true, the new windows will always be put in the center of the active " + "screen of the monitor." +@@ -380,11 +380,11 @@ msgstr "" + "Indien waar zullen nieuwe vensters steeds in het midden van het actieve " + "scherm van de monitor geplaatst worden." + +-#: data/org.gnome.mutter.gschema.xml.in:106 ++#: data/org.gnome.mutter.gschema.xml.in:107 + msgid "Enable experimental features" + msgstr "Experimentele functies inschakelen" + +-#: data/org.gnome.mutter.gschema.xml.in:107 ++#: data/org.gnome.mutter.gschema.xml.in:108 + msgid "" + "To enable experimental features, add the feature keyword to the list. " + "Whether the feature requires restarting the compositor depends on the given " +@@ -396,7 +396,10 @@ msgid "" + "manage HiDPI monitors. Does not require a restart. • “kms-modifiers” — makes " + "mutter always allocate scanout buffers with explicit modifiers, if supported " + "by the driver. Requires a restart. • “autoclose-xwayland” — automatically " +-"terminates Xwayland if all relevant X11 clients are gone. Requires a restart." ++"terminates Xwayland if all relevant X11 clients are gone. Requires a " ++"restart. • “variable-refresh-rate” — makes mutter dynamically adjust the " ++"refresh rate of the monitor when applicable if supported by the monitor, GPU " ++"and DRM driver. Configurable in Settings. Requires a restart." + msgstr "" + "Voeg het sleutelwoord van een experimentele functie toe aan de lijst om deze " + "in te schakelen. Of de compositor herstart moet worden vooraleer de functie " +@@ -411,21 +414,25 @@ msgstr "" + "scanoutbuffers steeds toewijst met uitdrukkelijke modifiers, indien dit " + "ondersteund wordt door het stuurprogramma. Hiervoor is opnieuw opstarten " + "vereist. • “autoclose-xwayland” — beëindigt Xwayland automatisch wanneer er " +-"geen relevante X11-cliënten meer zijn. Hiervoor is opnieuw opstarten vereist." ++"geen relevante X11-cliënten meer zijn. Hiervoor is opnieuw opstarten " ++"vereist. • “variable-refresh-rate” — laat mutter de verversingsfrequentie " ++"van het beeldscherm dynamisch aanpassen wanneer van toepassing en wanneer " ++"het beeldscherm, de GPU en het DRM-stuurprogramma dit ondersteunt. " ++"Instelbaar via Instellingen. Hiervoor is opnieuw opstarten vereist." + +-#: data/org.gnome.mutter.gschema.xml.in:137 ++#: data/org.gnome.mutter.gschema.xml.in:144 + msgid "Modifier to use to locate the pointer" + msgstr "Controletoets om de muispijl te lokaliseren" + +-#: data/org.gnome.mutter.gschema.xml.in:138 ++#: data/org.gnome.mutter.gschema.xml.in:145 + msgid "This key will initiate the “locate pointer” action." + msgstr "Deze sleutel activeert de actie ‘muispijl lokaliseren’." + +-#: data/org.gnome.mutter.gschema.xml.in:145 ++#: data/org.gnome.mutter.gschema.xml.in:152 + msgid "Timeout for check-alive ping" + msgstr "Time-out voor levenscontroleping" + +-#: data/org.gnome.mutter.gschema.xml.in:146 ++#: data/org.gnome.mutter.gschema.xml.in:153 + msgid "" + "Number of milliseconds a client has to respond to a ping request in order to " + "not be detected as frozen. Using 0 will disable the alive check completely." +@@ -434,15 +441,15 @@ msgstr "" + "verzoek om niet als bevroren beschouwd te worden. Stel dit in op 0 om de " + "levenscontrole volledig uit te schakelen." + +-#: data/org.gnome.mutter.gschema.xml.in:170 ++#: data/org.gnome.mutter.gschema.xml.in:177 + msgid "Switch monitor configurations" + msgstr "Tussen beeldschermconfiguraties schakelen" + +-#: data/org.gnome.mutter.gschema.xml.in:175 ++#: data/org.gnome.mutter.gschema.xml.in:182 + msgid "Rotates the built-in monitor configuration" + msgstr "Roteert de ingebouwde beeldschermconfiguratie" + +-#: data/org.gnome.mutter.gschema.xml.in:180 ++#: data/org.gnome.mutter.gschema.xml.in:187 + msgid "Cancel any active input capture session" + msgstr "Alle actieve sessies voor opvang van invoer annuleren" + +@@ -632,76 +639,76 @@ msgstr "%s %s" + msgid "Bell event" + msgstr "Bel-gebeurtenis" + +-#: src/core/display.c:733 ++#: src/core/display.c:734 + msgid "Privacy Screen Enabled" + msgstr "Privacyscherm ingeschakeld" + +-#: src/core/display.c:734 ++#: src/core/display.c:735 + msgid "Privacy Screen Disabled" + msgstr "Privacyscherm uitgeschakeld" + +-#: src/core/meta-context-main.c:594 ++#: src/core/meta-context-main.c:601 + msgid "Replace the running window manager" + msgstr "De huidige toepassing voor vensterbeheer vervangen" + +-#: src/core/meta-context-main.c:600 ++#: src/core/meta-context-main.c:607 + msgid "X Display to use" + msgstr "De te gebruiken X-weergave" + +-#: src/core/meta-context-main.c:606 ++#: src/core/meta-context-main.c:613 + msgid "Disable connection to session manager" + msgstr "Schakel de verbinding met het sessiebeheer uit" + +-#: src/core/meta-context-main.c:612 ++#: src/core/meta-context-main.c:619 + msgid "Specify session management ID" + msgstr "Bepaal de ID van het sessiebeheer" + +-#: src/core/meta-context-main.c:618 ++#: src/core/meta-context-main.c:625 + msgid "Initialize session from savefile" + msgstr "Initialiseer de sessie middels een opslagbestand" + +-#: src/core/meta-context-main.c:624 ++#: src/core/meta-context-main.c:631 + msgid "Make X calls synchronous" + msgstr "X-aanroepen synchroon maken" + +-#: src/core/meta-context-main.c:632 ++#: src/core/meta-context-main.c:639 + msgid "Run as a wayland compositor" + msgstr "Uitvoeren als een wayland compositor" + +-#: src/core/meta-context-main.c:638 ++#: src/core/meta-context-main.c:645 + msgid "Run as a nested compositor" + msgstr "Uitvoeren als een geneste compositor" + +-#: src/core/meta-context-main.c:644 ++#: src/core/meta-context-main.c:651 + msgid "Run wayland compositor without starting Xwayland" + msgstr "Wayland-compositor uitvoeren zonder Xwayland te starten" + +-#: src/core/meta-context-main.c:650 ++#: src/core/meta-context-main.c:657 + msgid "Specify Wayland display name to use" + msgstr "Bepaal de te gebruiken Wayland-weergavenaam" + +-#: src/core/meta-context-main.c:658 ++#: src/core/meta-context-main.c:665 + msgid "Run as a full display server, rather than nested" + msgstr "Uitvoeren als een volledige displayserver, in plaats van genest" + +-#: src/core/meta-context-main.c:663 ++#: src/core/meta-context-main.c:670 + msgid "Run as a headless display server" + msgstr "Uitvoeren als een ‘headless’ displayserver" + +-#: src/core/meta-context-main.c:668 ++#: src/core/meta-context-main.c:675 + msgid "Add persistent virtual monitor (WxH or WxH@R)" + msgstr "Blijvend virtueel beeldscherm toevoegen (BxH of BxH@V)" + +-#: src/core/meta-context-main.c:680 ++#: src/core/meta-context-main.c:687 + msgid "Run with X11 backend" + msgstr "Uitvoeren met X11-backend" + + # Onduidelijk of 'profile' hier een naamwoord of werkwoord is - Nathan +-#: src/core/meta-context-main.c:686 ++#: src/core/meta-context-main.c:693 + msgid "Profile performance using trace instrumentation" + msgstr "Prestaties profileren met trace-hulpmiddelen" + +-#: src/core/meta-context-main.c:692 ++#: src/core/meta-context-main.c:699 + msgid "Enable debug control D-Bus interface" + msgstr "Debugbesturing van D-Bus-interface inschakelen" + +@@ -737,7 +744,7 @@ msgstr "Versie-informatie tonen" + msgid "Mutter plugin to use" + msgstr "Te gebruiken Mutter-plug-in" + +-#: src/core/prefs.c:1843 ++#: src/core/prefs.c:1842 + #, c-format + msgid "Workspace %d" + msgstr "Werkblad %d" +@@ -746,7 +753,7 @@ msgstr "Werkblad %d" + msgid "Mutter was compiled without support for verbose mode" + msgstr "Mutter is gecompileerd zonder ondersteuning voor verbose-mode" + +-#: src/core/workspace.c:511 ++#: src/core/workspace.c:510 + msgid "Workspace switched" + msgstr "Werkblad gewisseld" + +@@ -776,7 +783,7 @@ msgstr "Scherm %d op beeldscherm ‘%s’ is ongeldig" + + #. This probably means that a non-WM compositor like xcompmgr is running; + #. * we have no way to get it to exit +-#: src/x11/meta-x11-display.c:2539 ++#: src/x11/meta-x11-display.c:2547 + #, c-format + msgid "" + "Another compositing manager is already running on screen %i on display “%s”." +@@ -789,7 +796,7 @@ msgstr "" + msgid "Format %s not supported" + msgstr "Formaat %s wordt niet ondersteund" + +-#: src/x11/window-props.c:524 ++#: src/x11/window-props.c:528 + #, c-format + msgid "%s (on %s)" + msgstr "%s (op %s)" +diff --git a/po/pa.po b/po/pa.po +index 29df49a3f..aeaed0f6d 100644 +--- a/po/pa.po ++++ b/po/pa.po +@@ -5,23 +5,23 @@ + # + # Amanpreet_Singh , 2004. + # Amanpreet Singh Alam , 2004. +-# A S Alam , 2006. ++# SPDX-FileCopyrightText: 2006, 2024 A S Alam + # A S Alam , 2007, 2009, 2010, 2011, 2021, 2023. + # ASB , 2007. + # Amanpreet Singh Alam , 2009, 2012, 2013, 2014, 2015, 2017, 2020. + msgid "" + msgstr "" + "Project-Id-Version: metacity.gnome-2-26\n" +-"Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/mutter/issues\n" +-"POT-Creation-Date: 2023-07-15 01:02+0000\n" +-"PO-Revision-Date: 2023-09-02 08:25-0700\n" +-"Last-Translator: A S Alam \n" ++"Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/mutter/issues/\n" ++"POT-Creation-Date: 2024-03-02 12:22+0000\n" ++"PO-Revision-Date: 2024-03-18 08:12-0500\n" ++"Last-Translator: A S Alam \n" + "Language-Team: Punjabi \n" + "Language: pa\n" + "MIME-Version: 1.0\n" + "Content-Type: text/plain; charset=UTF-8\n" + "Content-Transfer-Encoding: 8bit\n" +-"X-Generator: Lokalize 23.04.3\n" ++"X-Generator: Lokalize 23.08.5\n" + "Plural-Forms: nplurals=2; plural=(n != 1);\n" + "\n" + +@@ -162,22 +162,18 @@ msgid "Switch to last workspace" + msgstr "ਪਿਛਲੇ ਵਰਕਸਪੇਸ ਵਿੱਚ ਜਾਓ" + + #: data/50-mutter-navigation.xml:131 +-#| msgid "Move to workspace on the left" + msgid "Switch to workspace on the left" + msgstr "ਖੱਬੇ ਪਾਸੇ ਆਲੇ ਵਰਕਸਪੇਸ ਉੱਤੇ ਜਾਓ" + + #: data/50-mutter-navigation.xml:134 +-#| msgid "Move to workspace on the right" + msgid "Switch to workspace on the right" + msgstr "ਸੱਜੇ ਪਾਸੇ ਆਲੇ ਵਰਕਸਪੇਸ ਉੱਤੇ ਜਾਓ" + + #: data/50-mutter-navigation.xml:138 +-#| msgid "Switch to workspace 1" + msgid "Switch to workspace above" + msgstr "ਉੱਤਲੇ ਵਰਕਸਪੇਸ ਉੱਤੇ ਜਾਓ" + + #: data/50-mutter-navigation.xml:142 +-#| msgid "Switch to workspace 1" + msgid "Switch to workspace below" + msgstr "ਹੇਠਲੇ ਵਰਕਸਪੇਸ ਉੱਤੇ ਜਾਓ" + +@@ -257,11 +253,11 @@ msgstr "ਵਿੰਡੋ ਖੜਵੇਂ ਰੂਪ ਵਿੱਚ ਵੱਧੋ-ਵ + msgid "Maximize window horizontally" + msgstr "ਵਿੰਡੋ ਲੇਟਵੇਂ ਰੂਪ ਵਿੱਚ ਵੱਧੋ-ਵੱਧ" + +-#: data/50-mutter-windows.xml:41 data/org.gnome.mutter.gschema.xml.in:164 ++#: data/50-mutter-windows.xml:41 data/org.gnome.mutter.gschema.xml.in:167 + msgid "View split on left" + msgstr "ਖੱਬੇ ਪਾਸੇ ਵੰਡ ਵੇਖੋ" + +-#: data/50-mutter-windows.xml:45 data/org.gnome.mutter.gschema.xml.in:169 ++#: data/50-mutter-windows.xml:45 data/org.gnome.mutter.gschema.xml.in:172 + msgid "View split on right" + msgstr "ਸੱਜੇ ਪਾਸੇ ਵੰਡ ਵੇਖੋ" + +@@ -404,13 +400,12 @@ msgstr "ਤਜਰਬੇ ਅਧੀਨ ਫ਼ੀਚਰਾਂ ਨੂੰ ਸਮਰ + #| "framebuffer” — makes mutter default to layout logical monitors in a " + #| "logical pixel coordinate space, while scaling monitor framebuffers " + #| "instead of window content, to manage HiDPI monitors. Does not require a " +-#| "restart. • “rt-scheduler” — makes mutter request a low priority real-time " +-#| "scheduling. The executable or user must have CAP_SYS_NICE. Requires a " +-#| "restart. • “dma-buf-screen-sharing\" — enables DMA buffered screen " +-#| "sharing. This is already enabled by default when using the i915 driver, " +-#| "but disabled for everything else. Requires a restart. • “autoclose-" +-#| "xwayland” — automatically terminates Xwayland if all relevant X11 clients " +-#| "are gone. Does not require a restart." ++#| "restart. • “kms-modifiers” — makes mutter always allocate scanout buffers " ++#| "with explicit modifiers, if supported by the driver. Requires a restart. " ++#| "• “rt-scheduler” — makes mutter request a low priority real-time " ++#| "scheduling. Requires a restart. • “autoclose-xwayland” — automatically " ++#| "terminates Xwayland if all relevant X11 clients are gone. Requires a " ++#| "restart." + msgid "" + "To enable experimental features, add the feature keyword to the list. " + "Whether the feature requires restarting the compositor depends on the given " +@@ -421,10 +416,11 @@ msgid "" + "space, while scaling monitor framebuffers instead of window content, to " + "manage HiDPI monitors. Does not require a restart. • “kms-modifiers” — makes " + "mutter always allocate scanout buffers with explicit modifiers, if supported " +-"by the driver. Requires a restart. • “rt-scheduler” — makes mutter request a " +-"low priority real-time scheduling. Requires a restart. • “autoclose-" +-"xwayland” — automatically terminates Xwayland if all relevant X11 clients " +-"are gone. Requires a restart." ++"by the driver. Requires a restart. • “autoclose-xwayland” — automatically " ++"terminates Xwayland if all relevant X11 clients are gone. Requires a " ++"restart. • “variable-refresh-rate” — makes mutter dynamically adjust the " ++"refresh rate of the monitor when applicable if supported by the monitor, GPU " ++"and DRM driver. Configurable in Settings. Requires a restart." + msgstr "" + "ਤਜਰਬੇ ਅਧੀਨ ਫ਼ੀਚਰ ਸਮਰੱਥ ਕਰਨ ਲਈ, ਫੀਚਰ ਕੀਵਰਡ ਸੂਚੀ ਵਿੱਚ ਜੋੜੋ। ਕੀ ਫ਼ੀਚਰ ਲਈ" + " ਕੰਪੋਜ਼ੀਟਰ ਮੁੜ-ਚਾਲੂ " +@@ -437,24 +433,29 @@ msgstr "" + "ਲਾਜ਼ੀਕਲ ਮਾਨੀਟਰਾਂ ਦੇ ਖਾਕੇ ਲਈ ਮਟਰ ਨੂੰ ਮੂਲ ਬਣਾਉਣਾ, ਜਦੋਂ ਕਿ HiDPI ਮਾਨੀਟਰਾਂ ਦੇ" + " ਇੰਤਜ਼ਾਮ ਲਈ ਵਿੰਡੋ " + "ਸਮੱਗਰੀ ਦੀ ਬਜਾਏ ਸਕੇਲਿੰਗ ਮਾਨੀਟਰ ਫਰੇਮਬਰਫ਼ ਹੋਣ। ਮੁੜ-ਚਾਲੂ ਕਰਨ ਦੀ ਲੋੜ ਨਹੀਂ ਹੈ। •" +-" “kms-modifiers” — ਜੇ ਡਰਾਇਵਰ ਰਾਹੀਂ ਸਹਾਇਕ ਹੋਵੇ ਤਾਂ ਮਟਰ ਖਾਸ ਸੋਧਕਾਂ ਲਈ ਸਕੈਨ-ਆਉਟ" +-" ਬਫ਼ਰ ਹਮੇਸ਼ਾਂ ਜਾਰੀ ਕਰਦਾ ਹੈ। ਮੁੜ-ਚਾਲੂ ਕਰਨ ਦੀ ਲੋੜ ਹੈ। • “autoclose-xwayland” — ਜੇ" +-" ਸਾਰੇ ਵਾਜਬ X11 ਕਲਾਈਂਟ ਖਤਮ ਹੋ ਜਾਣ ਤਾਂ ਆਪਣੇ-ਆਪ Xwayland ਨੂੰ ਖਤਮ ਕਰਦਾ ਹੈ।" +-" ਮੁੜ-ਚਾਲੂ ਕਰਨ ਦੀ ਲੋੜ ਹੈ।" +- +-#: data/org.gnome.mutter.gschema.xml.in:141 ++" “kms-" ++"modifiers” — ਜੇ ਡਰਾਇਵਰ ਰਾਹੀਂ ਸਹਾਇਕ ਹੋਵੇ ਤਾਂ ਮਟਰ ਖਾਸ ਸੋਧਕਾਂ ਲਈ ਸਕੈਨ-ਆਉਟ ਬਫ਼ਰ" ++" ਹਮੇਸ਼ਾਂ ਜਾਰੀ " ++"ਕਰਦਾ ਹੈ। ਮੁੜ-ਚਾਲੂ ਕਰਨ ਦੀ ਲੋੜ ਹੈ। • “autoclose-xwayland” — ਜੇ ਸਾਰੇ ਵਾਜਬ X11" ++" ਕਲਾਈਂਟ " ++"ਖਤਮ ਹੋ ਜਾਣ ਤਾਂ ਆਪਣੇ-ਆਪ Xwayland ਨੂੰ ਖਤਮ ਕਰਦਾ ਹੈ। ਮੁੜ-ਚਾਲੂ ਕਰਨ ਦੀ ਲੋੜ ਹੈ। •" ++" “variable-refresh-rate” — ਜੇ ਮਾਨੀਟਰ, GPU ਅਤੇ DRM ਡਰਾਇਵਰ ਸਹਾਇਕ ਹੋਣ ਤਾਂ ਮਟਰ" ++" ਮਾਨੀਟਰ ਦੇ ਡਾਇਨੈਮਿਕ ਰੂਪ ਵਿੱਚ ਰੀ-ਫਰੈਸ਼ ਰੇਟ ਨੂੰ ਅਡਜੱਸਟ ਕਰ ਸਕਦਾ ਹੈ। ਸੈਟਿੰਗਾਂ ਵਿੱਚ" ++" ਸੰਰਚਨਾ ਕੀਤੀ ਜਾ ਸਕਦੀ ਹੈ। ਮੁੜ-ਚਾਲੂ ਕਰਨ ਦੀ ਲੋੜ ਹੈ।" ++ ++#: data/org.gnome.mutter.gschema.xml.in:144 + msgid "Modifier to use to locate the pointer" + msgstr "ਪੁਆਇੰਟਰ ਲੱਭਣ ਲਈ ਵਰਤਣ ਵਾਸਤੇ ਸੋਧਣ" + +-#: data/org.gnome.mutter.gschema.xml.in:142 ++#: data/org.gnome.mutter.gschema.xml.in:145 + msgid "This key will initiate the “locate pointer” action." + msgstr "ਇਹ ਕੁੰਜੀ “ਪੁਆਇੰਟਰ ਲੱਭੋ“ ਕਾਰਵਾਈ ਸ਼ੁਰੂ ਕਰਦੀ ਹੈ।" + +-#: data/org.gnome.mutter.gschema.xml.in:149 ++#: data/org.gnome.mutter.gschema.xml.in:152 + msgid "Timeout for check-alive ping" + msgstr "ਚੈਕ-ਸਰਗਰਮ ਪਿੰਗ ਲਈ ਸਮਾਂ-ਅੰਤਰਾਲ" + +-#: data/org.gnome.mutter.gschema.xml.in:150 ++#: data/org.gnome.mutter.gschema.xml.in:153 + msgid "" + "Number of milliseconds a client has to respond to a ping request in order to " + "not be detected as frozen. Using 0 will disable the alive check completely." +@@ -464,15 +465,15 @@ msgstr "" + "ਜਕੜਿਆ ਨਾ ਖੋਜਿਆ ਜਾਵੇ। 0 ਵਰਤਣ ਨਾਲ ਸਰਗਰਮੀ ਦੀ ਜਾਂਚ ਨੂੰ ਪੂਰੀ ਤਰ੍ਹਾਂ ਅਸਮਰੱਥ ਕੀਤਾ" + " ਜਾਵੇਗਾ।" + +-#: data/org.gnome.mutter.gschema.xml.in:174 ++#: data/org.gnome.mutter.gschema.xml.in:177 + msgid "Switch monitor configurations" + msgstr "ਮਾਨੀਟਰ ਸੰਰਚਨਾ ਨੂੰ ਬਦਲੋ" + +-#: data/org.gnome.mutter.gschema.xml.in:179 ++#: data/org.gnome.mutter.gschema.xml.in:182 + msgid "Rotates the built-in monitor configuration" + msgstr "ਬਿਲਟ-ਇਨ ਮਾਨੀਟਰ ਸੰਰਚਨਾ ਨੂੰ ਘੁੰਮਾਓ" + +-#: data/org.gnome.mutter.gschema.xml.in:184 ++#: data/org.gnome.mutter.gschema.xml.in:187 + msgid "Cancel any active input capture session" + msgstr "ਕਿਸੇ ਵੀ ਸਰਗਰਮ ਇਨਪੁਟ ਸਕਰੀਨ ਕੈਪਚਰ ਕਰਨ ਵਾਲੇ ਸ਼ੈਸ਼ਨ ਨੂੰ ਰੱਦ ਕਰੋ" + +@@ -614,7 +615,8 @@ msgstr "" + msgid "Allow X11 clients with a different endianness to connect to Xwayland" + msgstr "" + "ਵੱਖਰੀ ਏਂਡੀਨੈਸਸ (endianess) ਵਾਲੇ X11 ਵਾਲੇ ਕਲਾਈਂਟਾਂ ਨੂੰ Xwayland ਨਾਲ ਕਨੈਕਟ ਹੋਣ" +-" ਦੀ ਇਜਾਜ਼ਤ ਦਿਓ" ++" ਦੀ ਇਜਾਜ਼ਤ " ++"ਦਿਓ" + + #: data/org.gnome.mutter.wayland.gschema.xml.in:131 + msgid "" +@@ -629,26 +631,26 @@ msgid "" + "take effect." + msgstr "" + +-#: src/backends/meta-monitor.c:253 ++#: src/backends/meta-monitor.c:251 + msgid "Built-in display" + msgstr "ਬਿਲਟ-ਇਨ ਡਿਸਪਲੇਅ" + +-#: src/backends/meta-monitor.c:280 ++#: src/backends/meta-monitor.c:278 + msgid "Unknown" + msgstr "ਅਣਜਾਣ" + +-#: src/backends/meta-monitor.c:282 ++#: src/backends/meta-monitor.c:280 + msgid "Unknown Display" + msgstr "ਅਣਜਾਣ ਡਿਸਪਲੇਅ" + +-#: src/backends/meta-monitor.c:290 ++#: src/backends/meta-monitor.c:288 + #, c-format + msgctxt "" + "This is a monitor vendor name, followed by a size in inches, like 'Dell 15\"'" + msgid "%s %s" + msgstr "%s %s" + +-#: src/backends/meta-monitor.c:298 ++#: src/backends/meta-monitor.c:296 + #, c-format + msgctxt "" + "This is a monitor vendor name followed by product/model name where size in " +@@ -660,78 +662,82 @@ msgstr "%s %s" + msgid "Bell event" + msgstr "ਘੰਟੀ ਈਵੈਂਟ" + +-#: src/core/display.c:718 ++#: src/core/display.c:734 + msgid "Privacy Screen Enabled" + msgstr "ਪਰਦੇਦਾਰੀ ਸਕਰੀਨ ਸਮਰੱਥ ਹੈ" + +-#: src/core/display.c:719 ++#: src/core/display.c:735 + msgid "Privacy Screen Disabled" + msgstr "ਪਰਦੇਦਾਰੀ ਸਕਰੀਨ ਅਸਮਰੱਥ ਹੈ" + +-#: src/core/meta-context-main.c:581 ++#: src/core/meta-context-main.c:601 + msgid "Replace the running window manager" + msgstr "ਚੱਲ ਰਹੇ ਵਿੰਡੋ ਮੈਨੇਜਰ ਨੂੰ ਬਦਲੋ" + +-#: src/core/meta-context-main.c:587 ++#: src/core/meta-context-main.c:607 + msgid "X Display to use" + msgstr "ਵਰਤਣ ਲਈ X ਡਿਸਪਲੇਅ" + +-#: src/core/meta-context-main.c:593 ++#: src/core/meta-context-main.c:613 + msgid "Disable connection to session manager" + msgstr "ਸ਼ੈਸ਼ਨ ਮੈਨੇਜਰ ਨਾਲ ਕੁਨੈਕਸ਼ਨ ਅਯੋਗ" + +-#: src/core/meta-context-main.c:599 ++#: src/core/meta-context-main.c:619 + msgid "Specify session management ID" + msgstr "ਸ਼ੈਸ਼ਨ ਪਰਬੰਧਨ ID ਦਿਓ" + +-#: src/core/meta-context-main.c:605 ++#: src/core/meta-context-main.c:625 + msgid "Initialize session from savefile" + msgstr "ਸੰਭਾਲੀ ਫਾਇਲ ਤੋਂ ਸ਼ੈਸ਼ਨ ਸ਼ੁਰੂ" + +-#: src/core/meta-context-main.c:611 ++#: src/core/meta-context-main.c:631 + msgid "Make X calls synchronous" + msgstr "X ਕਾਲ ਸੈਕਰੋਨਸ ਬਣਾਓ" + +-#: src/core/meta-context-main.c:619 ++#: src/core/meta-context-main.c:639 + msgid "Run as a wayland compositor" + msgstr "ਵੇਲੈਂਡ ਕੰਪੋਜ਼ਰ ਵਜੋਂ ਚਲਾਓ" + +-#: src/core/meta-context-main.c:625 ++#: src/core/meta-context-main.c:645 + msgid "Run as a nested compositor" + msgstr "ਨੈਸਟਡ ਕੰਪੋਜ਼ਰ ਵਜੋਂ ਚਲਾਓ" + +-#: src/core/meta-context-main.c:631 ++#: src/core/meta-context-main.c:651 + msgid "Run wayland compositor without starting Xwayland" + msgstr "Xwayland ਸ਼ੁਰੂ ਕੀਤੇ ਬਿਨਾਂ ਵੇਲੈਂਡ ਕੰਪੋਜ਼ੀਟਰ ਚਲਾਓ" + +-#: src/core/meta-context-main.c:637 ++#: src/core/meta-context-main.c:657 + msgid "Specify Wayland display name to use" + msgstr "ਵਰਤਣ ਲਈ ਵੇਅਲੈਂਡ ਡਿਸਪਲੇਅ ਨਾਂ ਦਿਓ" + +-#: src/core/meta-context-main.c:645 ++#: src/core/meta-context-main.c:665 + msgid "Run as a full display server, rather than nested" + msgstr "ਅੰਦਰੂਨੀ ਰੂਪ ਵਿੱਚ ਚਲਾਉਣ ਦੀ ਬਜਾਏ ਪੂਰੇ ਡਿਸਪਲੇਅ ਸਰਵਰ ਵਜੋਂ ਚਲਾਓ" + +-#: src/core/meta-context-main.c:650 ++#: src/core/meta-context-main.c:670 + msgid "Run as a headless display server" + msgstr "ਹੈੱਡਲੈੱਸ ਡਿਸਪਲੇਅ ਸਰਵਰ ਵਜੋਂ ਚਲਾਓ" + +-#: src/core/meta-context-main.c:655 ++#: src/core/meta-context-main.c:675 + msgid "Add persistent virtual monitor (WxH or WxH@R)" + msgstr "ਸਥਿਰ ਵਰਚੁਅਲ ਮਾਨੀਟਰ ਜੋੜੋ (WxH or WxH@R)" + +-#: src/core/meta-context-main.c:667 ++#: src/core/meta-context-main.c:687 + msgid "Run with X11 backend" + msgstr "X11 ਬੈਕਐਡ ਨਾਲ ਚਲਾਓ" + +-#: src/core/meta-context-main.c:673 ++#: src/core/meta-context-main.c:693 + msgid "Profile performance using trace instrumentation" + msgstr "ਟਰੇਸ ਸਾਧਨ ਵਰਤ ਕੇ ਕਾਰਗੁਜ਼ਾਰੀ ਦਾ ਪਰੋਫ਼ਾਇਲ ਬਣਾਓ" + ++#: src/core/meta-context-main.c:699 ++msgid "Enable debug control D-Bus interface" ++msgstr "ਡੀਬੱਗ ਕੰਟਰੋਲ ਡੀ-ਬੱਸ ਇੰਟਰਫੇਸ ਸਮਰੱਥ ਕਰੋ" ++ + #. TRANSLATORS: This string refers to a button that switches between + #. * different modes. + #. +-#: src/core/meta-pad-action-mapper.c:861 ++#: src/core/meta-pad-action-mapper.c:826 + #, c-format + msgid "Mode Switch (Group %d)" + msgstr "ਮੋਡ ਬਦਲੋ (ਗਰੁੱਪ %d)" +@@ -739,16 +745,16 @@ msgstr "ਮੋਡ ਬਦਲੋ (ਗਰੁੱਪ %d)" + #. TRANSLATORS: This string refers to an action, cycles drawing tablets' + #. * mapping through the available outputs. + #. +-#: src/core/meta-pad-action-mapper.c:884 ++#: src/core/meta-pad-action-mapper.c:848 + msgid "Switch monitor" + msgstr "ਮਾਨੀਟਰ ਨੂੰ ਬਦਲੋ" + +-#: src/core/meta-pad-action-mapper.c:886 ++#: src/core/meta-pad-action-mapper.c:850 + msgid "Show on-screen help" + msgstr "ਆਨ-ਸਕਰੀਨ ਮਦਦ ਵੇਖੋ" + + #. Translators: this string will appear in Sysprof +-#: src/core/meta-profiler.c:111 src/core/meta-profiler.c:301 ++#: src/core/meta-profiler.c:109 src/core/meta-profiler.c:299 + msgid "Compositor" + msgstr "ਕੰਪੋਜੀਟਰ" + +@@ -760,7 +766,7 @@ msgstr "ਵਰਜਨ ਛਾਪੋ" + msgid "Mutter plugin to use" + msgstr "ਵਰਤਣ ਲਈ ਮੁੱਟਰ ਪਲੱਗਇਨ" + +-#: src/core/prefs.c:1843 ++#: src/core/prefs.c:1842 + #, c-format + msgid "Workspace %d" + msgstr "ਵਰਕਸਪੇਸ %d" +@@ -769,17 +775,16 @@ msgstr "ਵਰਕਸਪੇਸ %d" + msgid "Mutter was compiled without support for verbose mode" + msgstr "ਮੱਟਰ, ਵਰਬੋਜ਼ ਮੋਡ ਲਈ ਸਹਾਰੇ ਤੋਂ ਬਿਨਾਂ ਕੰਪਾਇਲ ਹੋਇਆ" + +-#: src/core/workspace.c:541 +-#| msgid "Workspace %s%d" ++#: src/core/workspace.c:510 + msgid "Workspace switched" + msgstr "ਵਰਕਸਪੇਸ ਬਦਲਿਆ" + +-#: src/wayland/meta-wayland-tablet-pad.c:530 ++#: src/wayland/meta-wayland-tablet-pad.c:532 + #, c-format + msgid "Mode Switch: Mode %d" + msgstr "ਮੋਡ ਬਦਲੋ: ਮੋਡ %d" + +-#: src/x11/meta-x11-display.c:708 ++#: src/x11/meta-x11-display.c:723 + #, c-format + msgid "" + "Display “%s” already has a window manager; try using the --replace option to " +@@ -789,19 +794,19 @@ msgstr "" + " ਬਦਲਣ ਲਈ --replace ਚੋਣ " + "ਵਰਤ ਕੇ ਦੇਖੋ।" + +-#: src/x11/meta-x11-display.c:1073 ++#: src/x11/meta-x11-display.c:1088 + #, c-format + msgid "Failed to open X Window System display “%s”" + msgstr "X ਵਿੰਡੋ ਸਿਸਟਮ ਡਿਸਪਲੇਅ ”%s” ਨੂੰ ਖੋਲਣ ਵਿੱਚ ਅਸਮਰਥ" + +-#: src/x11/meta-x11-display.c:1219 ++#: src/x11/meta-x11-display.c:1268 + #, c-format + msgid "Screen %d on display “%s” is invalid" + msgstr "ਡਿਸਪਲੇਅ ”%2$s” ਉੱਤੇ ਸਕਰੀਨ %1$d ਗਲਤ ਹੈ" + + #. This probably means that a non-WM compositor like xcompmgr is running; + #. * we have no way to get it to exit +-#: src/x11/meta-x11-display.c:2550 ++#: src/x11/meta-x11-display.c:2538 + #, c-format + msgid "" + "Another compositing manager is already running on screen %i on display “%s”." +@@ -814,7 +819,7 @@ msgstr "" + msgid "Format %s not supported" + msgstr "ਫਾਰਮੈਟ %s ਸਹਾਇਕ ਨਹੀਂ ਹੈ" + +-#: src/x11/window-props.c:548 ++#: src/x11/window-props.c:528 + #, c-format + msgid "%s (on %s)" + msgstr "%s (%s ਉੱਤੇ)" +@@ -2463,3 +2468,4 @@ msgstr "%s (%s ਉੱਤੇ)" + + #~ msgid "Theme file %s did not contain a root element" + #~ msgstr "ਥੀਮ ਫਾਇਲ %s root ਐਲੀਮੈਂਟ ਵਿੱਚ ਸ਼ਾਮਿਲ ਨਹੀਂ ਸੀ" ++ +diff --git a/src/backends/meta-color-device.c b/src/backends/meta-color-device.c +index 3032d34c2..0d6cf40db 100644 +--- a/src/backends/meta-color-device.c ++++ b/src/backends/meta-color-device.c +@@ -963,56 +963,62 @@ static void + create_device_profile_from_edid (MetaColorDevice *color_device, + GTask *task) + { +- const MetaEdidInfo *edid_info; ++ const MetaEdidInfo *edid_info = ++ meta_monitor_get_edid_info (color_device->monitor); ++ GenerateProfileData *data = g_task_get_task_data (task); ++ g_autoptr (CdIcc) cd_icc = NULL; ++ g_autoptr (GBytes) bytes = NULL; ++ g_autofree char *file_md5_checksum = NULL; ++ g_autoptr (GError) error = NULL; + +- edid_info = meta_monitor_get_edid_info (color_device->monitor); + if (edid_info) + { +- g_autoptr (CdIcc) cd_icc = NULL; +- GBytes *bytes; +- g_autoptr (GError) error = NULL; +- GenerateProfileData *data = g_task_get_task_data (task); +- const char *file_path = data->file_path; +- g_autofree char *file_md5_checksum = NULL; +- + meta_topic (META_DEBUG_COLOR, + "Generating ICC profile for '%s' from EDID", + meta_color_device_get_id (color_device)); + + cd_icc = create_icc_profile_from_edid (color_device, +- edid_info, file_path, ++ edid_info, data->file_path, + &error); +- if (!cd_icc) +- { +- g_task_return_error (task, g_steal_pointer (&error)); +- g_object_unref (task); +- return; +- } ++ } ++ else ++ { ++ meta_topic (META_DEBUG_COLOR, ++ "Generating sRGB ICC profile for '%s' because EDID is missing", ++ meta_color_device_get_id (color_device)); + +- bytes = cd_icc_save_data (cd_icc, CD_ICC_SAVE_FLAGS_NONE, &error); +- if (!bytes) +- { +- g_task_return_error (task, g_steal_pointer (&error)); +- g_object_unref (task); +- return; +- } ++ cd_icc = cd_icc_new (); + +- file_md5_checksum = g_compute_checksum_for_bytes (G_CHECKSUM_MD5, bytes); +- cd_icc_add_metadata (cd_icc, CD_PROFILE_METADATA_FILE_CHECKSUM, +- file_md5_checksum); ++ if (!cd_icc_create_default_full (cd_icc, ++ CD_ICC_LOAD_FLAGS_PRIMARIES, ++ &error)) ++ g_clear_object (&cd_icc); ++ } + +- data->color_calibration = +- meta_color_calibration_new (cd_icc, NULL); +- data->cd_icc = g_steal_pointer (&cd_icc); +- data->bytes = bytes; +- save_icc_profile (file_path, task); ++ if (!cd_icc) ++ { ++ g_task_return_error (task, g_steal_pointer (&error)); ++ g_object_unref (task); ++ return; + } +- else ++ ++ bytes = cd_icc_save_data (cd_icc, CD_ICC_SAVE_FLAGS_NONE, &error); ++ if (!bytes) + { +- g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED, +- "No EDID available"); ++ g_task_return_error (task, g_steal_pointer (&error)); + g_object_unref (task); ++ return; + } ++ ++ file_md5_checksum = g_compute_checksum_for_bytes (G_CHECKSUM_MD5, bytes); ++ cd_icc_add_metadata (cd_icc, CD_PROFILE_METADATA_FILE_CHECKSUM, ++ file_md5_checksum); ++ ++ data->color_calibration = ++ meta_color_calibration_new (cd_icc, NULL); ++ data->cd_icc = g_steal_pointer (&cd_icc); ++ data->bytes = g_steal_pointer (&bytes); ++ save_icc_profile (data->file_path, task); + } + + static void +diff --git a/src/backends/meta-stage-impl.c b/src/backends/meta-stage-impl.c +index 7aa24439d..727e1a5f3 100644 +--- a/src/backends/meta-stage-impl.c ++++ b/src/backends/meta-stage-impl.c +@@ -774,6 +774,8 @@ meta_stage_impl_redraw_view (ClutterStageWindow *stage_window, + { + g_autoptr (GError) error = NULL; + ++ clutter_frame_set_hint (frame, CLUTTER_FRAME_HINT_DIRECT_SCANOUT_ATTEMPTED); ++ + if (meta_stage_impl_scanout_view (stage_impl, + stage_view, + scanout, +diff --git a/src/backends/native/meta-cursor-renderer-native.c b/src/backends/native/meta-cursor-renderer-native.c +index 588caab28..83458cfe6 100644 +--- a/src/backends/native/meta-cursor-renderer-native.c ++++ b/src/backends/native/meta-cursor-renderer-native.c +@@ -454,7 +454,8 @@ create_cursor_drm_buffer_gbm (MetaGpuKms *gpu_kms, + GError **error) + { + struct gbm_bo *bo; +- uint8_t buf[4 * cursor_width * cursor_height]; ++ uint32_t bo_stride; ++ uint8_t *buf; + int i; + MetaDrmBufferFlags flags; + MetaDrmBufferGbm *buffer_gbm; +@@ -476,10 +477,16 @@ create_cursor_drm_buffer_gbm (MetaGpuKms *gpu_kms, + return NULL; + } + +- memset (buf, 0, sizeof (buf)); ++ bo_stride = gbm_bo_get_stride (bo); ++ buf = g_alloca0 (bo_stride * cursor_height); ++ + for (i = 0; i < height; i++) +- memcpy (buf + i * 4 * cursor_width, pixels + i * stride, width * 4); +- if (gbm_bo_write (bo, buf, cursor_width * cursor_height * 4) != 0) ++ { ++ memcpy (buf + i * bo_stride, ++ pixels + i * stride, ++ MIN (bo_stride, stride)); ++ } ++ if (gbm_bo_write (bo, buf, bo_stride * cursor_height) != 0) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + "Failed write to gbm_bo: %s", g_strerror (errno)); +diff --git a/src/backends/native/meta-drm-buffer-gbm.c b/src/backends/native/meta-drm-buffer-gbm.c +index 161d43957..4ca8fb0f4 100644 +--- a/src/backends/native/meta-drm-buffer-gbm.c ++++ b/src/backends/native/meta-drm-buffer-gbm.c +@@ -186,7 +186,7 @@ lock_front_buffer (MetaDrmBufferGbm *buffer_gbm, + return FALSE; + } + +- return meta_drm_buffer_gbm_ensure_fb_id (META_DRM_BUFFER (buffer_gbm), error); ++ return TRUE; + } + + MetaDrmBufferGbm * +diff --git a/src/backends/native/meta-kms-impl-device.c b/src/backends/native/meta-kms-impl-device.c +index b15eee14d..05bc89e83 100644 +--- a/src/backends/native/meta-kms-impl-device.c ++++ b/src/backends/native/meta-kms-impl-device.c +@@ -1559,9 +1559,11 @@ meta_kms_impl_device_handle_update (MetaKmsImplDevice *impl_device, + meta_kms_update_merge_from (crtc_frame->pending_update, update); + meta_kms_update_free (update); + update = g_steal_pointer (&crtc_frame->pending_update); +- disarm_crtc_frame_deadline_timer (crtc_frame); + } + ++ if (crtc_frame->deadline.armed) ++ disarm_crtc_frame_deadline_timer (crtc_frame); ++ + meta_kms_device_handle_flush (priv->device, latch_crtc); + + feedback = do_process (impl_device, latch_crtc, update, flags); +diff --git a/src/backends/native/meta-kms.c b/src/backends/native/meta-kms.c +index 795008b21..70d1e792c 100644 +--- a/src/backends/native/meta-kms.c ++++ b/src/backends/native/meta-kms.c +@@ -63,6 +63,8 @@ struct _MetaKms + int kernel_thread_inhibit_count; + + MetaKmsCursorManager *cursor_manager; ++ ++ gboolean shutting_down; + }; + + G_DEFINE_TYPE (MetaKms, meta_kms, META_TYPE_THREAD) +@@ -354,6 +356,7 @@ static void + on_prepare_shutdown (MetaBackend *backend, + MetaKms *kms) + { ++ kms->shutting_down = TRUE; + meta_kms_run_impl_task_sync (kms, prepare_shutdown_in_impl, NULL, NULL); + meta_thread_flush_callbacks (META_THREAD (kms)); + +@@ -408,6 +411,12 @@ meta_kms_new (MetaBackend *backend, + return kms; + } + ++gboolean ++meta_kms_is_shutting_down (MetaKms *kms) ++{ ++ return kms->shutting_down; ++} ++ + static void + meta_kms_finalize (GObject *object) + { +diff --git a/src/backends/native/meta-kms.h b/src/backends/native/meta-kms.h +index 743401406..f6b19520b 100644 +--- a/src/backends/native/meta-kms.h ++++ b/src/backends/native/meta-kms.h +@@ -60,6 +60,8 @@ MetaKmsDevice * meta_kms_create_device (MetaKms *kms, + MetaKmsDeviceFlag flags, + GError **error); + ++gboolean meta_kms_is_shutting_down (MetaKms *kms); ++ + MetaKms * meta_kms_new (MetaBackend *backend, + MetaKmsFlags flags, + GError **error); +diff --git a/src/backends/native/meta-onscreen-native.c b/src/backends/native/meta-onscreen-native.c +index 086e217cb..9836663d0 100644 +--- a/src/backends/native/meta-onscreen-native.c ++++ b/src/backends/native/meta-onscreen-native.c +@@ -76,7 +76,7 @@ typedef struct _MetaOnscreenNativeSecondaryGpuState + + struct { + MetaDrmBufferDumb *current_dumb_fb; +- MetaDrmBufferDumb *dumb_fbs[2]; ++ MetaDrmBufferDumb *dumb_fbs[3]; + } cpu; + + gboolean noted_primary_gpu_copy_ok; +@@ -98,9 +98,13 @@ struct _MetaOnscreenNative + struct { + struct gbm_surface *surface; + MetaDrmBuffer *current_fb; ++ MetaDrmBuffer *posted_fb; + MetaDrmBuffer *next_fb; ++ MetaDrmBuffer *stalled_fb; + CoglScanout *current_scanout; ++ CoglScanout *posted_scanout; + CoglScanout *next_scanout; ++ CoglScanout *stalled_scanout; + } gbm; + + #ifdef HAVE_EGL_DEVICE +@@ -125,6 +129,16 @@ struct _MetaOnscreenNative + gulong privacy_screen_changed_handler_id; + gulong color_space_changed_handler_id; + gulong hdr_metadata_changed_handler_id; ++ ++ gboolean needs_flush; ++ ++ unsigned int swaps_pending; ++ ++ struct { ++ int *rectangles; /* 4 x n_rectangles */ ++ int n_rectangles; ++ ClutterFrame *frame; ++ } next_post; + }; + + G_DEFINE_TYPE (MetaOnscreenNative, meta_onscreen_native, +@@ -132,44 +146,42 @@ G_DEFINE_TYPE (MetaOnscreenNative, meta_onscreen_native, + + static GQuark blit_source_quark = 0; + ++static void ++try_post_latest_swap (CoglOnscreen *onscreen); ++ ++static void ++post_finish_frame (MetaOnscreenNative *onscreen_native, ++ MetaKmsUpdate *kms_update); ++ + static gboolean + init_secondary_gpu_state (MetaRendererNative *renderer_native, + CoglOnscreen *onscreen, + GError **error); + +-static void +-free_current_bo (CoglOnscreen *onscreen) +-{ +- MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen); +- +- g_clear_object (&onscreen_native->gbm.current_fb); +- g_clear_object (&onscreen_native->gbm.current_scanout); +-} +- + static void + meta_onscreen_native_swap_drm_fb (CoglOnscreen *onscreen) + { + MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen); + +- if (!onscreen_native->gbm.next_fb) ++ if (!onscreen_native->gbm.posted_fb) + return; + +- free_current_bo (onscreen); ++ g_set_object (&onscreen_native->gbm.current_fb, ++ onscreen_native->gbm.posted_fb); ++ g_clear_object (&onscreen_native->gbm.posted_fb); + +- g_set_object (&onscreen_native->gbm.current_fb, onscreen_native->gbm.next_fb); +- g_clear_object (&onscreen_native->gbm.next_fb); + g_set_object (&onscreen_native->gbm.current_scanout, +- onscreen_native->gbm.next_scanout); +- g_clear_object (&onscreen_native->gbm.next_scanout); ++ onscreen_native->gbm.posted_scanout); ++ g_clear_object (&onscreen_native->gbm.posted_scanout); + } + + static void +-meta_onscreen_native_clear_next_fb (CoglOnscreen *onscreen) ++meta_onscreen_native_clear_posted_fb (CoglOnscreen *onscreen) + { + MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen); + +- g_clear_object (&onscreen_native->gbm.next_fb); +- g_clear_object (&onscreen_native->gbm.next_scanout); ++ g_clear_object (&onscreen_native->gbm.posted_fb); ++ g_clear_object (&onscreen_native->gbm.posted_scanout); + } + + static void +@@ -207,7 +219,7 @@ meta_onscreen_native_notify_frame_complete (CoglOnscreen *onscreen) + + info = cogl_onscreen_pop_head_frame_info (onscreen); + +- g_assert (!cogl_onscreen_peek_head_frame_info (onscreen)); ++ g_return_if_fail (info); + + _cogl_onscreen_notify_frame_sync (onscreen, info); + _cogl_onscreen_notify_complete (onscreen, info); +@@ -243,6 +255,7 @@ notify_view_crtc_presented (MetaRendererView *view, + + meta_onscreen_native_notify_frame_complete (onscreen); + meta_onscreen_native_swap_drm_fb (onscreen); ++ try_post_latest_swap (onscreen); + } + + static void +@@ -292,15 +305,13 @@ page_flip_feedback_ready (MetaKmsCrtc *kms_crtc, + CoglFramebuffer *framebuffer = + clutter_stage_view_get_onscreen (CLUTTER_STAGE_VIEW (view)); + CoglOnscreen *onscreen = COGL_ONSCREEN (framebuffer); +- MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen); + CoglFrameInfo *frame_info; + + frame_info = cogl_onscreen_peek_head_frame_info (onscreen); + frame_info->flags |= COGL_FRAME_INFO_FLAG_SYMBOLIC; + +- g_warn_if_fail (!onscreen_native->gbm.next_fb); +- + meta_onscreen_native_notify_frame_complete (onscreen); ++ try_post_latest_swap (onscreen); + } + + static void +@@ -350,7 +361,8 @@ page_flip_feedback_discarded (MetaKmsCrtc *kms_crtc, + frame_info->flags |= COGL_FRAME_INFO_FLAG_SYMBOLIC; + + meta_onscreen_native_notify_frame_complete (onscreen); +- meta_onscreen_native_clear_next_fb (onscreen); ++ meta_onscreen_native_clear_posted_fb (onscreen); ++ try_post_latest_swap (onscreen); + } + + static const MetaKmsPageFlipListenerVtable page_flip_listener_vtable = { +@@ -411,18 +423,41 @@ custom_egl_stream_page_flip (gpointer custom_page_flip_data, + } + #endif /* HAVE_EGL_DEVICE */ + +-void +-meta_onscreen_native_dummy_power_save_page_flip (CoglOnscreen *onscreen) ++static void ++drop_stalled_swap (CoglOnscreen *onscreen) + { + CoglFrameInfo *frame_info; ++ MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen); + +- meta_onscreen_native_swap_drm_fb (onscreen); ++ /* Remember we can't compare stalled_fb because it's not used by ++ * META_RENDERER_NATIVE_MODE_EGL_DEVICE. So we judge stalled to be whenever ++ * swaps_pending > 1. ++ */ ++ if (onscreen_native->swaps_pending <= 1) ++ return; ++ ++ onscreen_native->swaps_pending--; ++ ++ g_clear_object (&onscreen_native->gbm.stalled_fb); ++ g_clear_object (&onscreen_native->gbm.stalled_scanout); + + frame_info = cogl_onscreen_peek_tail_frame_info (onscreen); + frame_info->flags |= COGL_FRAME_INFO_FLAG_SYMBOLIC; + meta_onscreen_native_notify_frame_complete (onscreen); + } + ++void ++meta_onscreen_native_dummy_power_save_page_flip (CoglOnscreen *onscreen) ++{ ++ drop_stalled_swap (onscreen); ++ ++ /* If the monitor just woke up and the shell is fully idle (has nothing ++ * more to swap) then we just woke to an indefinitely black screen. Let's ++ * fix that using the last swap (which is never classified as "stalled"). ++ */ ++ try_post_latest_swap (onscreen); ++} ++ + static void + apply_transform (MetaCrtcKms *crtc_kms, + MetaKmsPlaneAssignment *kms_plane_assignment, +@@ -506,6 +541,8 @@ meta_onscreen_native_flip_crtc (CoglOnscreen *onscreen, + MetaGpuKms *gpu_kms; + MetaDrmBuffer *buffer; + MetaKmsPlaneAssignment *plane_assignment; ++ graphene_rect_t src_rect; ++ MtkRectangle dst_rect; + + COGL_TRACE_BEGIN_SCOPED (MetaOnscreenNativeFlipCrtcs, + "Meta::OnscreenNative::flip_crtc()"); +@@ -519,16 +556,21 @@ meta_onscreen_native_flip_crtc (CoglOnscreen *onscreen, + switch (renderer_gpu_data->mode) + { + case META_RENDERER_NATIVE_MODE_GBM: +- graphene_rect_t src_rect; +- MtkRectangle dst_rect; ++ g_set_object (&onscreen_native->gbm.posted_fb, ++ onscreen_native->gbm.next_fb); ++ g_clear_object (&onscreen_native->gbm.next_fb); ++ ++ buffer = onscreen_native->gbm.posted_fb; + +- buffer = onscreen_native->gbm.next_fb; ++ g_set_object (&onscreen_native->gbm.posted_scanout, ++ onscreen_native->gbm.next_scanout); ++ g_clear_object (&onscreen_native->gbm.next_scanout); + +- if (onscreen_native->gbm.next_scanout) ++ if (onscreen_native->gbm.posted_scanout) + { +- cogl_scanout_get_src_rect (onscreen_native->gbm.next_scanout, ++ cogl_scanout_get_src_rect (onscreen_native->gbm.posted_scanout, + &src_rect); +- cogl_scanout_get_dst_rect (onscreen_native->gbm.next_scanout, ++ cogl_scanout_get_dst_rect (onscreen_native->gbm.posted_scanout, + &dst_rect); + } + else +@@ -919,12 +961,17 @@ static MetaDrmBufferDumb * + secondary_gpu_get_next_dumb_buffer (MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state) + { + MetaDrmBufferDumb *current_dumb_fb; ++ const int n_dumb_fbs = G_N_ELEMENTS (secondary_gpu_state->cpu.dumb_fbs); ++ int i; + + current_dumb_fb = secondary_gpu_state->cpu.current_dumb_fb; +- if (current_dumb_fb == secondary_gpu_state->cpu.dumb_fbs[0]) +- return secondary_gpu_state->cpu.dumb_fbs[1]; +- else +- return secondary_gpu_state->cpu.dumb_fbs[0]; ++ for (i = 0; i < n_dumb_fbs; i++) ++ { ++ if (current_dumb_fb == secondary_gpu_state->cpu.dumb_fbs[i]) ++ return secondary_gpu_state->cpu.dumb_fbs[(i + 1) % n_dumb_fbs]; ++ } ++ ++ return secondary_gpu_state->cpu.dumb_fbs[0]; + } + + static MetaDrmBuffer * +@@ -1256,10 +1303,17 @@ swap_buffer_result_feedback (const MetaKmsFeedback *kms_feedback, + g_warning ("Page flip failed: %s", error->message); + + frame_info = cogl_onscreen_peek_head_frame_info (onscreen); +- frame_info->flags |= COGL_FRAME_INFO_FLAG_SYMBOLIC; + +- meta_onscreen_native_notify_frame_complete (onscreen); +- meta_onscreen_native_clear_next_fb (onscreen); ++ /* After resuming from suspend, drop_stalled_swap might have done this ++ * already and emptied the frame_info queue. ++ */ ++ if (frame_info) ++ { ++ frame_info->flags |= COGL_FRAME_INFO_FLAG_SYMBOLIC; ++ meta_onscreen_native_notify_frame_complete (onscreen); ++ } ++ ++ meta_onscreen_native_clear_posted_fb (onscreen); + } + + static const MetaKmsResultListenerVtable swap_buffer_result_listener_vtable = { +@@ -1280,35 +1334,58 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen, + CoglRendererEGL *cogl_renderer_egl = cogl_renderer->winsys; + MetaRendererNativeGpuData *renderer_gpu_data = cogl_renderer_egl->platform; + MetaRendererNative *renderer_native = renderer_gpu_data->renderer_native; +- MetaRenderer *renderer = META_RENDERER (renderer_native); +- MetaBackend *backend = meta_renderer_get_backend (renderer); +- MetaMonitorManager *monitor_manager = +- meta_backend_get_monitor_manager (backend); + MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen); ++ MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state; + MetaGpuKms *render_gpu = onscreen_native->render_gpu; + MetaDeviceFile *render_device_file; + ClutterFrame *frame = user_data; +- MetaFrameNative *frame_native = meta_frame_native_from_frame (frame); +- MetaKmsUpdate *kms_update; + CoglOnscreenClass *parent_class; ++ gboolean create_timestamp_query = TRUE; + gboolean egl_context_changed = FALSE; +- MetaPowerSave power_save_mode; + g_autoptr (GError) error = NULL; + MetaDrmBufferFlags buffer_flags; + MetaDrmBufferGbm *buffer_gbm; + g_autoptr (MetaDrmBuffer) primary_gpu_fb = NULL; + g_autoptr (MetaDrmBuffer) secondary_gpu_fb = NULL; +- MetaKmsCrtc *kms_crtc; +- MetaKmsDevice *kms_device; ++ size_t rectangles_size; + + COGL_TRACE_BEGIN_SCOPED (MetaRendererNativeSwapBuffers, + "Meta::OnscreenNative::swap_buffers_with_damage()"); + ++ if (meta_is_topic_enabled (META_DEBUG_KMS)) ++ { ++ unsigned int frames_pending = ++ cogl_onscreen_count_pending_frames (onscreen); ++ ++ meta_topic (META_DEBUG_KMS, ++ "Swap buffers: %u frames pending (%s-buffering)", ++ frames_pending, ++ frames_pending == 1 ? "double" : ++ frames_pending == 2 ? "triple" : ++ "?"); ++ } ++ + secondary_gpu_fb = + update_secondary_gpu_state_pre_swap_buffers (onscreen, + rectangles, + n_rectangles); + ++ secondary_gpu_state = onscreen_native->secondary_gpu_state; ++ if (secondary_gpu_state) ++ { ++ MetaRendererNativeGpuData *secondary_gpu_data; ++ ++ secondary_gpu_data = ++ meta_renderer_native_get_gpu_data (renderer_native, ++ secondary_gpu_state->gpu_kms); ++ if (secondary_gpu_data->secondary.copy_mode == ++ META_SHARED_FRAMEBUFFER_COPY_MODE_SECONDARY_GPU) ++ create_timestamp_query = FALSE; ++ } ++ ++ if (create_timestamp_query) ++ cogl_onscreen_egl_maybe_create_timestamp_query (onscreen, frame_info); ++ + parent_class = COGL_ONSCREEN_CLASS (meta_onscreen_native_parent_class); + parent_class->swap_buffers_with_damage (onscreen, + rectangles, +@@ -1362,11 +1439,32 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen, + switch (renderer_gpu_data->mode) + { + case META_RENDERER_NATIVE_MODE_GBM: +- g_warn_if_fail (onscreen_native->gbm.next_fb == NULL); ++ if (onscreen_native->gbm.next_fb != NULL) ++ { ++ g_warn_if_fail (onscreen_native->gbm.stalled_fb == NULL); ++ drop_stalled_swap (onscreen); ++ g_assert (onscreen_native->gbm.stalled_fb == NULL); ++ onscreen_native->gbm.stalled_fb = ++ g_steal_pointer (&onscreen_native->gbm.next_fb); ++ onscreen_native->gbm.stalled_scanout = ++ g_steal_pointer (&onscreen_native->gbm.next_scanout); ++ } ++ + if (onscreen_native->secondary_gpu_state) + g_set_object (&onscreen_native->gbm.next_fb, secondary_gpu_fb); + else + g_set_object (&onscreen_native->gbm.next_fb, primary_gpu_fb); ++ ++ if (!meta_drm_buffer_ensure_fb_id (onscreen_native->gbm.next_fb, &error)) ++ { ++ g_warning ("Failed to ensure KMS FB ID on %s: %s", ++ meta_device_file_get_path (render_device_file), ++ error->message); ++ ++ frame_info->flags |= COGL_FRAME_INFO_FLAG_SYMBOLIC; ++ meta_onscreen_native_notify_frame_complete (onscreen); ++ return; ++ } + break; + case META_RENDERER_NATIVE_MODE_SURFACELESS: + break; +@@ -1376,6 +1474,9 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen, + #endif + } + ++ clutter_frame_set_result (frame, ++ CLUTTER_FRAME_RESULT_PENDING_PRESENTED); ++ + /* + * If we changed EGL context, cogl will have the wrong idea about what is + * current, making it fail to set it when it needs to. Avoid that by making +@@ -1385,12 +1486,78 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen, + if (egl_context_changed) + _cogl_winsys_egl_ensure_current (cogl_display); + +- kms_crtc = meta_crtc_kms_get_kms_crtc (META_CRTC_KMS (onscreen_native->crtc)); +- kms_device = meta_kms_crtc_get_device (kms_crtc); ++ rectangles_size = n_rectangles * 4 * sizeof (int); ++ onscreen_native->next_post.rectangles = ++ g_realloc (onscreen_native->next_post.rectangles, rectangles_size); ++ memcpy (onscreen_native->next_post.rectangles, rectangles, rectangles_size); ++ onscreen_native->next_post.n_rectangles = n_rectangles; ++ ++ g_clear_pointer (&onscreen_native->next_post.frame, clutter_frame_unref); ++ onscreen_native->next_post.frame = clutter_frame_ref (frame); ++ ++ onscreen_native->swaps_pending++; ++ try_post_latest_swap (onscreen); ++} ++ ++static void ++try_post_latest_swap (CoglOnscreen *onscreen) ++{ ++ CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen); ++ CoglContext *cogl_context = cogl_framebuffer_get_context (framebuffer); ++ CoglRenderer *cogl_renderer = cogl_context->display->renderer; ++ CoglRendererEGL *cogl_renderer_egl = cogl_renderer->winsys; ++ MetaRendererNativeGpuData *renderer_gpu_data = cogl_renderer_egl->platform; ++ MetaRendererNative *renderer_native = renderer_gpu_data->renderer_native; ++ MetaRenderer *renderer = META_RENDERER (renderer_native); ++ MetaBackend *backend = meta_renderer_get_backend (renderer); ++ MetaBackendNative *backend_native = META_BACKEND_NATIVE (backend); ++ MetaKms *kms = meta_backend_native_get_kms (backend_native); ++ MetaMonitorManager *monitor_manager = ++ meta_backend_get_monitor_manager (backend); ++ MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen); ++ MetaPowerSave power_save_mode; ++ MetaCrtcKms *crtc_kms = META_CRTC_KMS (onscreen_native->crtc); ++ MetaKmsCrtc *kms_crtc = meta_crtc_kms_get_kms_crtc (crtc_kms); ++ MetaKmsDevice *kms_device = meta_kms_crtc_get_device (kms_crtc); ++ MetaKmsUpdate *kms_update; ++ g_autoptr (MetaKmsFeedback) kms_feedback = NULL; ++ g_autoptr (ClutterFrame) frame = NULL; ++ MetaFrameNative *frame_native; ++ ++ if (onscreen_native->next_post.frame == NULL || ++ onscreen_native->view == NULL || ++ meta_kms_is_shutting_down (kms)) ++ return; + + power_save_mode = meta_monitor_manager_get_power_save_mode (monitor_manager); + if (power_save_mode == META_POWER_SAVE_ON) + { ++ unsigned int frames_pending = ++ cogl_onscreen_count_pending_frames (onscreen); ++ unsigned int posts_pending; ++ ++ g_assert (frames_pending >= onscreen_native->swaps_pending); ++ posts_pending = frames_pending - onscreen_native->swaps_pending; ++ if (posts_pending > 0) ++ return; /* wait for the next frame notification and then try again */ ++ ++ frame = g_steal_pointer (&onscreen_native->next_post.frame); ++ frame_native = meta_frame_native_from_frame (frame); ++ ++ if (onscreen_native->swaps_pending == 0) ++ { ++ if (frame_native) ++ { ++ kms_update = meta_frame_native_steal_kms_update (frame_native); ++ if (kms_update) ++ post_finish_frame (onscreen_native, kms_update); ++ } ++ return; ++ } ++ ++ drop_stalled_swap (onscreen); ++ onscreen_native->swaps_pending--; ++ + kms_update = meta_frame_native_ensure_kms_update (frame_native, + kms_device); + meta_kms_update_add_result_listener (kms_update, +@@ -1405,15 +1572,13 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen, + onscreen_native->crtc, + kms_update, + META_KMS_ASSIGN_PLANE_FLAG_NONE, +- rectangles, +- n_rectangles); ++ onscreen_native->next_post.rectangles, ++ onscreen_native->next_post.n_rectangles); + } + else + { + meta_renderer_native_queue_power_save_page_flip (renderer_native, + onscreen); +- clutter_frame_set_result (frame, +- CLUTTER_FRAME_RESULT_PENDING_PRESENTED); + return; + } + +@@ -1433,8 +1598,6 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen, + kms_update = meta_frame_native_steal_kms_update (frame_native); + meta_renderer_native_queue_mode_set_update (renderer_native, + kms_update); +- clutter_frame_set_result (frame, +- CLUTTER_FRAME_RESULT_PENDING_PRESENTED); + return; + } + else if (meta_renderer_native_has_pending_mode_set (renderer_native)) +@@ -1448,8 +1611,6 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen, + + meta_frame_native_steal_kms_update (frame_native); + meta_renderer_native_post_mode_set_updates (renderer_native); +- clutter_frame_set_result (frame, +- CLUTTER_FRAME_RESULT_PENDING_PRESENTED); + return; + } + break; +@@ -1465,8 +1626,6 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen, + kms_update); + + meta_renderer_native_post_mode_set_updates (renderer_native); +- clutter_frame_set_result (frame, +- CLUTTER_FRAME_RESULT_PENDING_PRESENTED); + return; + } + break; +@@ -1481,7 +1640,6 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen, + kms_update = meta_frame_native_steal_kms_update (frame_native); + meta_kms_device_post_update (kms_device, kms_update, + META_KMS_UPDATE_FLAG_NONE); +- clutter_frame_set_result (frame, CLUTTER_FRAME_RESULT_PENDING_PRESENTED); + } + + gboolean +@@ -1552,7 +1710,7 @@ scanout_result_feedback (const MetaKmsFeedback *kms_feedback, + + g_warning ("Direct scanout page flip failed: %s", error->message); + +- cogl_scanout_notify_failed (onscreen_native->gbm.next_scanout, ++ cogl_scanout_notify_failed (onscreen_native->gbm.posted_scanout, + onscreen); + clutter_stage_view_add_redraw_clip (view, NULL); + clutter_stage_view_schedule_update_now (view); +@@ -1562,7 +1720,7 @@ scanout_result_feedback (const MetaKmsFeedback *kms_feedback, + frame_info->flags |= COGL_FRAME_INFO_FLAG_SYMBOLIC; + + meta_onscreen_native_notify_frame_complete (onscreen); +- meta_onscreen_native_clear_next_fb (onscreen); ++ meta_onscreen_native_clear_posted_fb (onscreen); + } + + static const MetaKmsResultListenerVtable scanout_result_listener_vtable = { +@@ -1614,6 +1772,18 @@ meta_onscreen_native_direct_scanout (CoglOnscreen *onscreen, + return FALSE; + } + ++ /* Our direct scanout frame counts as 1, so more than that means we would ++ * be jumping the queue (and post would fail). ++ */ ++ if (cogl_onscreen_count_pending_frames (onscreen) > 1) ++ { ++ g_set_error_literal (error, ++ COGL_SCANOUT_ERROR, ++ COGL_SCANOUT_ERROR_INHIBITED, ++ "Direct scanout is inhibited during triple buffering"); ++ return FALSE; ++ } ++ + renderer_gpu_data = meta_renderer_native_get_gpu_data (renderer_native, + render_gpu); + +@@ -1729,11 +1899,7 @@ meta_onscreen_native_before_redraw (CoglOnscreen *onscreen, + ClutterFrame *frame) + { + MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen); +- MetaCrtcKms *crtc_kms = META_CRTC_KMS (onscreen_native->crtc); +- MetaKmsCrtc *kms_crtc = meta_crtc_kms_get_kms_crtc (crtc_kms); + +- meta_kms_device_await_flush (meta_kms_crtc_get_device (kms_crtc), +- kms_crtc); + maybe_update_frame_sync (onscreen_native, frame); + } + +@@ -1849,22 +2015,79 @@ meta_onscreen_native_finish_frame (CoglOnscreen *onscreen, + MetaKmsDevice *kms_device = meta_kms_crtc_get_device (kms_crtc); + MetaFrameNative *frame_native = meta_frame_native_from_frame (frame); + MetaKmsUpdate *kms_update; ++ unsigned int frames_pending = cogl_onscreen_count_pending_frames (onscreen); ++ unsigned int swaps_pending = onscreen_native->swaps_pending; ++ unsigned int posts_pending = frames_pending - swaps_pending; + +- kms_update = meta_frame_native_steal_kms_update (frame_native); +- if (!kms_update) ++ onscreen_native->needs_flush |= meta_kms_device_handle_flush (kms_device, ++ kms_crtc); ++ ++ if (!meta_frame_native_has_kms_update (frame_native)) + { +- if (meta_kms_device_handle_flush (kms_device, kms_crtc)) +- { +- kms_update = meta_kms_update_new (kms_device); +- meta_kms_update_set_flushing (kms_update, kms_crtc); +- } +- else ++ if (!onscreen_native->needs_flush || posts_pending) + { + clutter_frame_set_result (frame, CLUTTER_FRAME_RESULT_IDLE); + return; + } + } + ++ if (posts_pending && !swaps_pending) ++ { ++ g_return_if_fail (meta_frame_native_has_kms_update (frame_native)); ++ g_warn_if_fail (onscreen_native->next_post.frame == NULL); ++ ++ g_clear_pointer (&onscreen_native->next_post.frame, clutter_frame_unref); ++ onscreen_native->next_post.frame = clutter_frame_ref (frame); ++ clutter_frame_set_result (frame, CLUTTER_FRAME_RESULT_PENDING_PRESENTED); ++ return; ++ } ++ ++ kms_update = meta_frame_native_steal_kms_update (frame_native); ++ ++ if (posts_pending && swaps_pending) ++ { ++ MetaFrameNative *older_frame_native; ++ MetaKmsUpdate *older_kms_update; ++ ++ g_return_if_fail (kms_update); ++ g_return_if_fail (onscreen_native->next_post.frame != NULL); ++ ++ older_frame_native = ++ meta_frame_native_from_frame (onscreen_native->next_post.frame); ++ older_kms_update = ++ meta_frame_native_ensure_kms_update (older_frame_native, kms_device); ++ meta_kms_update_merge_from (older_kms_update, kms_update); ++ meta_kms_update_free (kms_update); ++ clutter_frame_set_result (frame, CLUTTER_FRAME_RESULT_IDLE); ++ return; ++ } ++ ++ if (!kms_update) ++ { ++ kms_update = meta_kms_update_new (kms_device); ++ g_warn_if_fail (onscreen_native->needs_flush); ++ } ++ ++ if (onscreen_native->needs_flush) ++ { ++ meta_kms_update_set_flushing (kms_update, kms_crtc); ++ onscreen_native->needs_flush = FALSE; ++ } ++ ++ post_finish_frame (onscreen_native, kms_update); ++ ++ clutter_frame_set_result (frame, CLUTTER_FRAME_RESULT_PENDING_PRESENTED); ++} ++ ++static void ++post_finish_frame (MetaOnscreenNative *onscreen_native, ++ MetaKmsUpdate *kms_update) ++{ ++ MetaCrtc *crtc = onscreen_native->crtc; ++ MetaKmsCrtc *kms_crtc = meta_crtc_kms_get_kms_crtc (META_CRTC_KMS (crtc)); ++ MetaKmsDevice *kms_device = meta_kms_crtc_get_device (kms_crtc); ++ g_autoptr (MetaKmsFeedback) kms_feedback = NULL; ++ + meta_kms_update_add_result_listener (kms_update, + &finish_frame_result_listener_vtable, + NULL, +@@ -1887,7 +2110,19 @@ meta_onscreen_native_finish_frame (CoglOnscreen *onscreen, + meta_kms_update_set_flushing (kms_update, kms_crtc); + meta_kms_device_post_update (kms_device, kms_update, + META_KMS_UPDATE_FLAG_NONE); +- clutter_frame_set_result (frame, CLUTTER_FRAME_RESULT_PENDING_PRESENTED); ++} ++ ++void ++meta_onscreen_native_discard_pending_swaps (CoglOnscreen *onscreen) ++{ ++ MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen); ++ ++ onscreen_native->swaps_pending = 0; ++ ++ g_clear_object (&onscreen_native->gbm.stalled_fb); ++ g_clear_object (&onscreen_native->gbm.stalled_scanout); ++ g_clear_object (&onscreen_native->gbm.next_fb); ++ g_clear_object (&onscreen_native->gbm.next_scanout); + } + + static gboolean +@@ -2802,8 +3037,11 @@ meta_onscreen_native_dispose (GObject *object) + { + case META_RENDERER_NATIVE_MODE_GBM: + g_clear_object (&onscreen_native->gbm.next_fb); ++ g_clear_object (&onscreen_native->gbm.posted_fb); ++ g_clear_object (&onscreen_native->gbm.current_fb); + g_clear_object (&onscreen_native->gbm.next_scanout); +- free_current_bo (onscreen); ++ g_clear_object (&onscreen_native->gbm.posted_scanout); ++ g_clear_object (&onscreen_native->gbm.current_scanout); + break; + case META_RENDERER_NATIVE_MODE_SURFACELESS: + g_assert_not_reached (); +@@ -2837,6 +3075,10 @@ meta_onscreen_native_dispose (GObject *object) + + g_clear_object (&onscreen_native->output); + g_clear_object (&onscreen_native->crtc); ++ ++ g_clear_pointer (&onscreen_native->next_post.rectangles, g_free); ++ g_clear_pointer (&onscreen_native->next_post.frame, clutter_frame_unref); ++ onscreen_native->next_post.n_rectangles = 0; + } + + static void +diff --git a/src/backends/native/meta-onscreen-native.h b/src/backends/native/meta-onscreen-native.h +index 0e1193325..e30357d19 100644 +--- a/src/backends/native/meta-onscreen-native.h ++++ b/src/backends/native/meta-onscreen-native.h +@@ -48,6 +48,8 @@ void meta_onscreen_native_dummy_power_save_page_flip (CoglOnscreen *onscreen); + gboolean meta_onscreen_native_is_buffer_scanout_compatible (CoglOnscreen *onscreen, + CoglScanout *scanout); + ++void meta_onscreen_native_discard_pending_swaps (CoglOnscreen *onscreen); ++ + void meta_onscreen_native_set_view (CoglOnscreen *onscreen, + MetaRendererView *view); + +diff --git a/src/backends/native/meta-renderer-native.c b/src/backends/native/meta-renderer-native.c +index aa76d018c..3c22b4e86 100644 +--- a/src/backends/native/meta-renderer-native.c ++++ b/src/backends/native/meta-renderer-native.c +@@ -731,12 +731,18 @@ static gboolean + dummy_power_save_page_flip_cb (gpointer user_data) + { + MetaRendererNative *renderer_native = user_data; ++ GList *old_list = ++ g_steal_pointer (&renderer_native->power_save_page_flip_onscreens); + +- g_list_foreach (renderer_native->power_save_page_flip_onscreens, ++ g_list_foreach (old_list, + (GFunc) meta_onscreen_native_dummy_power_save_page_flip, + NULL); +- g_clear_list (&renderer_native->power_save_page_flip_onscreens, ++ g_clear_list (&old_list, + g_object_unref); ++ ++ if (renderer_native->power_save_page_flip_onscreens != NULL) ++ return G_SOURCE_CONTINUE; ++ + renderer_native->power_save_page_flip_source_id = 0; + + return G_SOURCE_REMOVE; +@@ -748,6 +754,9 @@ meta_renderer_native_queue_power_save_page_flip (MetaRendererNative *renderer_na + { + const unsigned int timeout_ms = 100; + ++ if (g_list_find (renderer_native->power_save_page_flip_onscreens, onscreen)) ++ return; ++ + if (!renderer_native->power_save_page_flip_source_id) + { + renderer_native->power_save_page_flip_source_id = +@@ -1529,6 +1538,26 @@ detach_onscreens (MetaRenderer *renderer) + } + } + ++static void ++discard_pending_swaps (MetaRenderer *renderer) ++{ ++ GList *views = meta_renderer_get_views (renderer);; ++ GList *l; ++ ++ for (l = views; l; l = l->next) ++ { ++ ClutterStageView *stage_view = l->data; ++ CoglFramebuffer *fb = clutter_stage_view_get_onscreen (stage_view); ++ CoglOnscreen *onscreen; ++ ++ if (!COGL_IS_ONSCREEN (fb)) ++ continue; ++ ++ onscreen = COGL_ONSCREEN (fb); ++ meta_onscreen_native_discard_pending_swaps (onscreen); ++ } ++} ++ + static void + meta_renderer_native_rebuild_views (MetaRenderer *renderer) + { +@@ -1539,6 +1568,7 @@ meta_renderer_native_rebuild_views (MetaRenderer *renderer) + MetaRendererClass *parent_renderer_class = + META_RENDERER_CLASS (meta_renderer_native_parent_class); + ++ discard_pending_swaps (renderer); + meta_kms_discard_pending_page_flips (kms); + g_hash_table_remove_all (renderer_native->mode_set_updates); + +diff --git a/src/backends/x11/nested/meta-stage-x11-nested.c b/src/backends/x11/nested/meta-stage-x11-nested.c +index 41b78ec18..02eccc77d 100644 +--- a/src/backends/x11/nested/meta-stage-x11-nested.c ++++ b/src/backends/x11/nested/meta-stage-x11-nested.c +@@ -186,6 +186,8 @@ meta_stage_x11_nested_finish_frame (ClutterStageWindow *stage_window, + } + + frame_info = cogl_frame_info_new (context, 0); ++ cogl_onscreen_egl_maybe_create_timestamp_query (stage_x11->onscreen, ++ frame_info); + cogl_onscreen_swap_buffers (stage_x11->onscreen, frame_info, frame); + + if (!clutter_frame_has_result (frame)) +diff --git a/src/compositor/edge-resistance.c b/src/compositor/edge-resistance.c +index f3005714b..338886255 100644 +--- a/src/compositor/edge-resistance.c ++++ b/src/compositor/edge-resistance.c +@@ -29,17 +29,6 @@ + #include "core/meta-workspace-manager-private.h" + #include "core/workspace-private.h" + +-/* A simple macro for whether a given window's edges are potentially +- * relevant for resistance/snapping during a move/resize operation +- */ +-#define WINDOW_EDGES_RELEVANT(window, display) \ +- meta_window_should_be_showing (window) && \ +- (!meta_compositor_get_current_window_drag (display->compositor) || \ +- window != meta_window_drag_get_window (meta_compositor_get_current_window_drag (display->compositor))) && \ +- window->type != META_WINDOW_DESKTOP && \ +- window->type != META_WINDOW_MENU && \ +- window->type != META_WINDOW_SPLASHSCREEN +- + typedef struct _MetaEdgeResistanceData MetaEdgeResistanceData; + + struct _MetaEdgeResistanceData +@@ -52,6 +41,29 @@ struct _MetaEdgeResistanceData + + static GQuark edge_resistance_data_quark = 0; + ++static gboolean ++is_window_relevant_for_edges (MetaWindow *window) ++{ ++ MetaWindowDrag *drag; ++ ++ if (!meta_window_should_be_showing (window)) ++ return FALSE; ++ ++ drag = meta_compositor_get_current_window_drag (window->display->compositor); ++ if (drag && window == meta_window_drag_get_window (drag)) ++ return FALSE; ++ ++ switch (window->type) ++ { ++ case META_WINDOW_DESKTOP: ++ case META_WINDOW_MENU: ++ case META_WINDOW_SPLASHSCREEN: ++ return FALSE; ++ default: ++ return TRUE; ++ } ++} ++ + /* !WARNING!: this function can return invalid indices (namely, either -1 or + * edges->len); this is by design, but you need to remember this. + */ +@@ -879,12 +891,13 @@ static MetaEdgeResistanceData * + compute_resistance_and_snapping_edges (MetaWindowDrag *window_drag) + { + MetaEdgeResistanceData *edge_data; +- GList *stacked_windows; +- GList *cur_window_iter; +- GList *edges; ++ GList *l; + /* Lists of window positions (rects) and their relative stacking positions */ + int stack_position; +- GSList *obscuring_windows, *window_stacking; ++ g_autoslist (MtkRectangle) obscuring_windows = NULL; ++ g_autoptr (GList) stacked_windows = NULL; ++ g_autoptr (GSList) window_stacking = NULL; ++ g_autoptr (GList) edges = NULL; + /* The portions of the above lists that still remain at the stacking position + * in the layer that we are working on + */ +@@ -910,16 +923,16 @@ compute_resistance_and_snapping_edges (MetaWindowDrag *window_drag) + * those below it instead of going both ways, we also need to keep a + * counter list. Messy, I know. + */ +- obscuring_windows = window_stacking = NULL; +- cur_window_iter = stacked_windows; + stack_position = 0; +- while (cur_window_iter != NULL) ++ for (l = stacked_windows; l; l = l->next) + { +- MetaWindow *cur_window = cur_window_iter->data; +- if (WINDOW_EDGES_RELEVANT (cur_window, display)) ++ MetaWindow *cur_window = l->data; ++ ++ if (is_window_relevant_for_edges (cur_window)) + { + MtkRectangle *new_rect; +- new_rect = g_new (MtkRectangle, 1); ++ ++ new_rect = mtk_rectangle_new_empty (); + meta_window_get_frame_rect (cur_window, new_rect); + obscuring_windows = g_slist_prepend (obscuring_windows, new_rect); + window_stacking = +@@ -927,7 +940,6 @@ compute_resistance_and_snapping_edges (MetaWindowDrag *window_drag) + } + + stack_position++; +- cur_window_iter = cur_window_iter->next; + } + /* Put 'em in bottom to top order */ + rem_windows = obscuring_windows = g_slist_reverse (obscuring_windows); +@@ -940,18 +952,17 @@ compute_resistance_and_snapping_edges (MetaWindowDrag *window_drag) + */ + edges = NULL; + stack_position = 0; +- cur_window_iter = stacked_windows; +- while (cur_window_iter != NULL) ++ for (l = stacked_windows; l; l = l->next) + { ++ MetaWindow *cur_window = l->data; + MtkRectangle cur_rect; +- MetaWindow *cur_window = cur_window_iter->data; + meta_window_get_frame_rect (cur_window, &cur_rect); + + /* Check if we want to use this window's edges for edge + * resistance (note that dock edges are considered screen edges + * which are handled separately + */ +- if (WINDOW_EDGES_RELEVANT (cur_window, display) && ++ if (is_window_relevant_for_edges (cur_window) && + cur_window->type != META_WINDOW_DOCK) + { + GList *new_edges; +@@ -1035,17 +1046,8 @@ compute_resistance_and_snapping_edges (MetaWindowDrag *window_drag) + } + + stack_position++; +- cur_window_iter = cur_window_iter->next; + } + +- /* +- * 4th: Free the extra memory not needed and sort the list +- */ +- g_list_free (stacked_windows); +- /* Free the memory used by the obscuring windows/docks lists */ +- g_slist_free (window_stacking); +- g_slist_free_full (obscuring_windows, g_free); +- + /* Sort the list. FIXME: Should I bother with this sorting? I just + * sort again later in cache_edges() anyway... + */ +@@ -1060,7 +1062,6 @@ compute_resistance_and_snapping_edges (MetaWindowDrag *window_drag) + edges, + workspace_manager->active_workspace->monitor_edges, + workspace_manager->active_workspace->screen_edges); +- g_list_free (edges); + + return edge_data; + } +diff --git a/src/compositor/meta-sync-ring.c b/src/compositor/meta-sync-ring.c +index 8f5d1a046..1267dd4f2 100644 +--- a/src/compositor/meta-sync-ring.c ++++ b/src/compositor/meta-sync-ring.c +@@ -544,20 +544,29 @@ gboolean + meta_sync_ring_insert_wait (void) + { + MetaSyncRing *ring = meta_sync_ring_get (); ++ MetaSync *sync; + + if (!ring) + return FALSE; + + g_return_val_if_fail (ring->xdisplay != NULL, FALSE); + +- if (ring->current_sync->state != META_SYNC_STATE_READY) ++ sync = ring->current_sync; ++ ++ if (sync->state == META_SYNC_STATE_WAITING) ++ { ++ meta_gl_delete_sync (sync->gpu_fence); ++ sync->gpu_fence = 0; ++ sync->state = META_SYNC_STATE_READY; ++ } ++ else if (sync->state != META_SYNC_STATE_READY) + { + meta_warning ("MetaSyncRing: Sync object is not ready -- were events handled properly?"); + if (!meta_sync_ring_reboot (ring->xdisplay)) + return FALSE; + } + +- meta_sync_insert (ring->current_sync); ++ meta_sync_insert (sync); + + return TRUE; + } +diff --git a/src/compositor/meta-window-actor-wayland.c b/src/compositor/meta-window-actor-wayland.c +index 24597bbf7..0b43f8eb1 100644 +--- a/src/compositor/meta-window-actor-wayland.c ++++ b/src/compositor/meta-window-actor-wayland.c +@@ -350,7 +350,7 @@ meta_window_actor_wayland_get_scanout_candidate (MetaWindowActor *actor) + ClutterActor *child_actor; + ClutterActorIter iter; + MetaSurfaceActor *topmost_surface_actor = NULL; +- int n_mapped_surfaces = 0; ++ int n_visible_surface_actors = 0; + MetaWindow *window; + ClutterActorBox window_box; + ClutterActorBox surface_box; +@@ -365,11 +365,17 @@ meta_window_actor_wayland_get_scanout_candidate (MetaWindowActor *actor) + clutter_actor_iter_init (&iter, surface_container); + while (clutter_actor_iter_next (&iter, &child_actor)) + { ++ MetaSurfaceActor *surface_actor; ++ + if (!clutter_actor_is_mapped (child_actor)) + continue; + +- topmost_surface_actor = META_SURFACE_ACTOR (child_actor); +- n_mapped_surfaces++; ++ surface_actor = META_SURFACE_ACTOR (child_actor); ++ if (meta_surface_actor_is_obscured (surface_actor)) ++ continue; ++ ++ topmost_surface_actor = surface_actor; ++ n_visible_surface_actors++; + } + + if (!topmost_surface_actor) +@@ -380,10 +386,10 @@ meta_window_actor_wayland_get_scanout_candidate (MetaWindowActor *actor) + } + + window = meta_window_actor_get_meta_window (actor); +- if (meta_window_is_fullscreen (window) && n_mapped_surfaces == 1) ++ if (meta_window_is_fullscreen (window) && n_visible_surface_actors == 1) + return topmost_surface_actor; + +- if (meta_window_is_fullscreen (window) && n_mapped_surfaces == 2) ++ if (meta_window_is_fullscreen (window) && n_visible_surface_actors == 2) + { + MetaSurfaceActorWayland *bg_surface_actor = NULL; + MetaWaylandSurface *bg_surface; +@@ -393,10 +399,16 @@ meta_window_actor_wayland_get_scanout_candidate (MetaWindowActor *actor) + clutter_actor_iter_init (&iter, surface_container); + while (clutter_actor_iter_next (&iter, &child_actor)) + { ++ MetaSurfaceActor *surface_actor; ++ + if (!clutter_actor_is_mapped (child_actor)) + continue; + +- bg_surface_actor = META_SURFACE_ACTOR_WAYLAND (child_actor); ++ surface_actor = META_SURFACE_ACTOR (child_actor); ++ if (meta_surface_actor_is_obscured (surface_actor)) ++ continue; ++ ++ bg_surface_actor = META_SURFACE_ACTOR_WAYLAND (surface_actor); + break; + } + g_assert (bg_surface_actor); +diff --git a/src/core/constraints.c b/src/core/constraints.c +index 9af0f0004..4bd2bc471 100644 +--- a/src/core/constraints.c ++++ b/src/core/constraints.c +@@ -1154,6 +1154,9 @@ constrain_modal_dialog (MetaWindow *window, + meta_window_get_placement_rule (window)) + return TRUE; + ++ if (window->fullscreen) ++ return TRUE; ++ + /* We want to center the dialog on the parent, including the decorations + for both of them. info->current is in client X window coordinates, so we need + to convert them to frame coordinates, apply the centering and then +diff --git a/src/core/window.c b/src/core/window.c +index 47c29d94a..c6c23708e 100644 +--- a/src/core/window.c ++++ b/src/core/window.c +@@ -2227,7 +2227,7 @@ meta_window_show (MetaWindow *window) + window->has_maximize_func) + { + MtkRectangle work_area; +- meta_window_get_work_area_for_monitor (window, window->monitor->number, &work_area); ++ meta_window_get_work_area_current_monitor (window, &work_area); + /* Automaximize windows that map with a size > MAX_UNMAXIMIZED_WINDOW_AREA of the work area */ + if (window->rect.width * window->rect.height > work_area.width * work_area.height * MAX_UNMAXIMIZED_WINDOW_AREA) + { +@@ -3101,7 +3101,7 @@ meta_window_unmaximize (MetaWindow *window, + MtkRectangle old_frame_rect, old_buffer_rect; + gboolean has_target_size; + +- meta_window_get_work_area_for_monitor (window, window->monitor->number, &work_area); ++ meta_window_get_work_area_current_monitor (window, &work_area); + meta_window_get_frame_rect (window, &old_frame_rect); + meta_window_get_buffer_rect (window, &old_buffer_rect); + +@@ -5837,9 +5837,7 @@ void + meta_window_get_work_area_current_monitor (MetaWindow *window, + MtkRectangle *area) + { +- meta_window_get_work_area_for_monitor (window, +- window->monitor->number, +- area); ++ meta_window_get_work_area_for_logical_monitor (window, window->monitor, area); + } + + /** +diff --git a/src/meson.build b/src/meson.build +index f7797ff9c..e658f98ca 100644 +--- a/src/meson.build ++++ b/src/meson.build +@@ -579,6 +579,8 @@ if have_wayland + 'core/meta-service-channel.h', + 'wayland/meta-cursor-sprite-wayland.c', + 'wayland/meta-cursor-sprite-wayland.h', ++ 'wayland/meta-drm-timeline.c', ++ 'wayland/meta-drm-timeline.h', + 'wayland/meta-pointer-confinement-wayland.c', + 'wayland/meta-pointer-confinement-wayland.h', + 'wayland/meta-pointer-lock-wayland.c', +@@ -633,6 +635,8 @@ if have_wayland + 'wayland/meta-wayland-keyboard.h', + 'wayland/meta-wayland-legacy-xdg-foreign.c', + 'wayland/meta-wayland-legacy-xdg-foreign.h', ++ 'wayland/meta-wayland-linux-drm-syncobj.c', ++ 'wayland/meta-wayland-linux-drm-syncobj.h', + 'wayland/meta-wayland-outputs.c', + 'wayland/meta-wayland-outputs.h', + 'wayland/meta-wayland-pointer.c', +@@ -1084,6 +1088,7 @@ if have_wayland + ['xdg-output', 'unstable', 'v1', ], + ['xdg-shell', 'stable', ], + ['xwayland-keyboard-grab', 'unstable', 'v1', ], ++ ['linux-drm-syncobj-v1', 'private', ], + ] + if have_wayland_eglstream + wayland_eglstream_protocols_dir = wayland_eglstream_protocols_dep.get_variable('pkgdatadir') +diff --git a/src/tests/mtk/region-tests.c b/src/tests/mtk/region-tests.c +index 7e41a53f0..c9ebc2adb 100644 +--- a/src/tests/mtk/region-tests.c ++++ b/src/tests/mtk/region-tests.c +@@ -26,7 +26,7 @@ test_contains_point (void) + { + g_autoptr (MtkRegion) r1 = NULL; + +- r1 = mtk_region_create_rectangle (mtk_rectangle_new (0, 0, 100, 100)); ++ r1 = mtk_region_create_rectangle (&MTK_RECTANGLE_INIT (0, 0, 100, 100)); + + g_assert (!mtk_region_contains_point (r1, 200, 200)); + g_assert (mtk_region_contains_point (r1, 50, 50)); +diff --git a/src/tests/native-kms-render.c b/src/tests/native-kms-render.c +index f5ebc23fe..2f870fdc3 100644 +--- a/src/tests/native-kms-render.c ++++ b/src/tests/native-kms-render.c +@@ -39,6 +39,8 @@ + #include "tests/meta-wayland-test-driver.h" + #include "tests/meta-wayland-test-utils.h" + ++#define N_FRAMES_PER_TEST 30 ++ + typedef struct + { + int number_of_frames_left; +@@ -46,12 +48,15 @@ typedef struct + + struct { + int n_paints; +- uint32_t fb_id; ++ int n_presentations; ++ int n_direct_scanouts; ++ GList *fb_ids; + } scanout; + + gboolean wait_for_scanout; + + struct { ++ int scanouts_attempted; + gboolean scanout_sabotaged; + gboolean fallback_painted; + guint repaint_guard_id; +@@ -101,7 +106,7 @@ meta_test_kms_render_basic (void) + gulong handler_id; + + test = (KmsRenderingTest) { +- .number_of_frames_left = 10, ++ .number_of_frames_left = N_FRAMES_PER_TEST, + .loop = g_main_loop_new (NULL, FALSE), + }; + handler_id = g_signal_connect (stage, "after-update", +@@ -123,7 +128,6 @@ on_scanout_before_update (ClutterStage *stage, + KmsRenderingTest *test) + { + test->scanout.n_paints = 0; +- test->scanout.fb_id = 0; + } + + static void +@@ -135,6 +139,7 @@ on_scanout_before_paint (ClutterStage *stage, + CoglScanout *scanout; + CoglScanoutBuffer *scanout_buffer; + MetaDrmBuffer *buffer; ++ uint32_t fb_id; + + scanout = clutter_stage_view_peek_scanout (stage_view); + if (!scanout) +@@ -143,8 +148,13 @@ on_scanout_before_paint (ClutterStage *stage, + scanout_buffer = cogl_scanout_get_buffer (scanout); + g_assert_true (META_IS_DRM_BUFFER (scanout_buffer)); + buffer = META_DRM_BUFFER (scanout_buffer); +- test->scanout.fb_id = meta_drm_buffer_get_fb_id (buffer); +- g_assert_cmpuint (test->scanout.fb_id, >, 0); ++ fb_id = meta_drm_buffer_get_fb_id (buffer); ++ g_assert_cmpuint (fb_id, >, 0); ++ test->scanout.fb_ids = g_list_append (test->scanout.fb_ids, ++ GUINT_TO_POINTER (fb_id)); ++ ++ /* Triple buffering, but no higher */ ++ g_assert_cmpuint (g_list_length (test->scanout.fb_ids), <=, 2); + } + + static void +@@ -173,12 +183,12 @@ on_scanout_presented (ClutterStage *stage, + MetaDeviceFile *device_file; + GError *error = NULL; + drmModeCrtc *drm_crtc; ++ uint32_t first_fb_id_expected; + +- if (test->wait_for_scanout && test->scanout.n_paints > 0) ++ if (test->wait_for_scanout && test->scanout.fb_ids == NULL) + return; + +- if (test->wait_for_scanout && test->scanout.fb_id == 0) +- return; ++ test->scanout.n_presentations++; + + device_pool = meta_backend_native_get_device_pool (backend_native); + +@@ -197,15 +207,41 @@ on_scanout_presented (ClutterStage *stage, + drm_crtc = drmModeGetCrtc (meta_device_file_get_fd (device_file), + meta_kms_crtc_get_id (kms_crtc)); + g_assert_nonnull (drm_crtc); +- if (test->scanout.fb_id == 0) +- g_assert_cmpuint (drm_crtc->buffer_id, !=, test->scanout.fb_id); ++ ++ if (test->scanout.fb_ids) ++ { ++ test->scanout.n_direct_scanouts++; ++ first_fb_id_expected = GPOINTER_TO_UINT (test->scanout.fb_ids->data); ++ test->scanout.fb_ids = g_list_delete_link (test->scanout.fb_ids, ++ test->scanout.fb_ids); ++ } + else +- g_assert_cmpuint (drm_crtc->buffer_id, ==, test->scanout.fb_id); ++ { ++ first_fb_id_expected = 0; ++ } ++ ++ /* The buffer ID won't match on the first frame because switching from ++ * triple buffered compositing to double buffered direct scanout takes ++ * an extra frame to drain the queue. Thereafter we are in direct scanout ++ * mode and expect the buffer IDs to match. ++ */ ++ if (test->scanout.n_presentations > 1) ++ { ++ if (first_fb_id_expected == 0) ++ g_assert_cmpuint (drm_crtc->buffer_id, !=, first_fb_id_expected); ++ else ++ g_assert_cmpuint (drm_crtc->buffer_id, ==, first_fb_id_expected); ++ } ++ + drmModeFreeCrtc (drm_crtc); + + meta_device_file_release (device_file); + +- g_main_loop_quit (test->loop); ++ test->number_of_frames_left--; ++ if (test->number_of_frames_left <= 0) ++ g_main_loop_quit (test->loop); ++ else ++ clutter_actor_queue_redraw (CLUTTER_ACTOR (stage)); + } + + typedef enum +@@ -244,7 +280,9 @@ meta_test_kms_render_client_scanout (void) + g_assert_nonnull (wayland_test_client); + + test = (KmsRenderingTest) { ++ .number_of_frames_left = N_FRAMES_PER_TEST, + .loop = g_main_loop_new (NULL, FALSE), ++ .scanout = {0}, + .wait_for_scanout = TRUE, + }; + +@@ -270,7 +308,8 @@ meta_test_kms_render_client_scanout (void) + clutter_actor_queue_redraw (CLUTTER_ACTOR (stage)); + g_main_loop_run (test.loop); + +- g_assert_cmpuint (test.scanout.fb_id, >, 0); ++ g_assert_cmpint (test.scanout.n_presentations, ==, N_FRAMES_PER_TEST); ++ g_assert_cmpint (test.scanout.n_direct_scanouts, ==, N_FRAMES_PER_TEST); + + g_debug ("Unmake fullscreen"); + window = meta_find_window_from_title (test_context, "dma-buf-scanout-test"); +@@ -292,10 +331,15 @@ meta_test_kms_render_client_scanout (void) + g_assert_cmpint (buffer_rect.y, ==, 10); + + test.wait_for_scanout = FALSE; ++ test.number_of_frames_left = N_FRAMES_PER_TEST; ++ test.scanout.n_presentations = 0; ++ test.scanout.n_direct_scanouts = 0; ++ + clutter_actor_queue_redraw (CLUTTER_ACTOR (stage)); + g_main_loop_run (test.loop); + +- g_assert_cmpuint (test.scanout.fb_id, ==, 0); ++ g_assert_cmpint (test.scanout.n_presentations, ==, N_FRAMES_PER_TEST); ++ g_assert_cmpint (test.scanout.n_direct_scanouts, ==, 0); + + g_debug ("Moving back to 0, 0"); + meta_window_move_frame (window, TRUE, 0, 0); +@@ -307,10 +351,15 @@ meta_test_kms_render_client_scanout (void) + g_assert_cmpint (buffer_rect.y, ==, 0); + + test.wait_for_scanout = TRUE; ++ test.number_of_frames_left = N_FRAMES_PER_TEST; ++ test.scanout.n_presentations = 0; ++ test.scanout.n_direct_scanouts = 0; ++ + clutter_actor_queue_redraw (CLUTTER_ACTOR (stage)); + g_main_loop_run (test.loop); + +- g_assert_cmpuint (test.scanout.fb_id, >, 0); ++ g_assert_cmpint (test.scanout.n_presentations, ==, N_FRAMES_PER_TEST); ++ g_assert_cmpint (test.scanout.n_direct_scanouts, ==, N_FRAMES_PER_TEST); + + g_signal_handler_disconnect (stage, before_update_handler_id); + g_signal_handler_disconnect (stage, before_paint_handler_id); +@@ -364,6 +413,15 @@ on_scanout_fallback_before_paint (ClutterStage *stage, + if (!scanout) + return; + ++ test->scanout_fallback.scanouts_attempted++; ++ ++ /* The first scanout candidate frame will get composited due to triple ++ * buffering draining the queue to drop to double buffering. So don't ++ * sabotage that first frame. ++ */ ++ if (test->scanout_fallback.scanouts_attempted < 2) ++ return; ++ + g_assert_false (test->scanout_fallback.scanout_sabotaged); + + if (is_atomic_mode_setting (kms_device)) +@@ -401,6 +459,15 @@ on_scanout_fallback_paint_view (ClutterStage *stage, + g_clear_handle_id (&test->scanout_fallback.repaint_guard_id, + g_source_remove); + test->scanout_fallback.fallback_painted = TRUE; ++ test->scanout_fallback.scanout_sabotaged = FALSE; ++ } ++ else if (test->scanout_fallback.scanouts_attempted == 1) ++ { ++ /* Now that we've seen the first scanout attempt that was inhibited by ++ * triple buffering, try a second frame. The second one should scanout ++ * and will be sabotaged. ++ */ ++ clutter_actor_queue_redraw (CLUTTER_ACTOR (stage)); + } + } + +@@ -410,11 +477,11 @@ on_scanout_fallback_presented (ClutterStage *stage, + ClutterFrameInfo *frame_info, + KmsRenderingTest *test) + { +- if (!test->scanout_fallback.scanout_sabotaged) +- return; ++ if (test->scanout_fallback.fallback_painted) ++ g_main_loop_quit (test->loop); + +- g_assert_true (test->scanout_fallback.fallback_painted); +- g_main_loop_quit (test->loop); ++ test->number_of_frames_left--; ++ g_assert_cmpint (test->number_of_frames_left, >, 0); + } + + static void +@@ -443,6 +510,7 @@ meta_test_kms_render_client_scanout_fallback (void) + g_assert_nonnull (wayland_test_client); + + test = (KmsRenderingTest) { ++ .number_of_frames_left = N_FRAMES_PER_TEST, + .loop = g_main_loop_new (NULL, FALSE), + }; + +diff --git a/src/tests/ref-tests/wayland_buffer_single-pixel-buffer_7.ref.png b/src/tests/ref-tests/wayland_buffer_single-pixel-buffer_7.ref.png +new file mode 100644 +index 000000000..c2b0c2ff5 +Binary files /dev/null and b/src/tests/ref-tests/wayland_buffer_single-pixel-buffer_7.ref.png differ +diff --git a/src/tests/ref-tests/wayland_buffer_single-pixel-buffer_8.ref.png b/src/tests/ref-tests/wayland_buffer_single-pixel-buffer_8.ref.png +new file mode 100644 +index 000000000..28fc7b266 +Binary files /dev/null and b/src/tests/ref-tests/wayland_buffer_single-pixel-buffer_8.ref.png differ +diff --git a/src/tests/wayland-test-clients/buffer-transform.c b/src/tests/wayland-test-clients/buffer-transform.c +index cb78489b4..8591e88d6 100644 +--- a/src/tests/wayland-test-clients/buffer-transform.c ++++ b/src/tests/wayland-test-clients/buffer-transform.c +@@ -22,11 +22,6 @@ + + #include "wayland-test-client-utils.h" + +-static WaylandDisplay *display; +-static struct wl_surface *surface; +-static struct xdg_surface *xdg_surface; +-static struct xdg_toplevel *xdg_toplevel; +- + static gboolean waiting_for_configure = FALSE; + static gboolean fullscreen = 0; + static uint32_t window_width = 0; +@@ -88,7 +83,9 @@ static const struct xdg_surface_listener xdg_surface_listener = { + }; + + static void +-draw_main (gboolean rotated) ++draw_main (WaylandDisplay *display, ++ struct wl_surface *surface, ++ gboolean rotated) + { + static uint32_t color0 = 0xffffffff; + static uint32_t color1 = 0xff00ffff; +@@ -139,7 +136,7 @@ draw_main (gboolean rotated) + } + + static void +-wait_for_configure (void) ++wait_for_configure (WaylandDisplay *display) + { + waiting_for_configure = TRUE; + while (waiting_for_configure || window_width == 0) +@@ -153,6 +150,11 @@ int + main (int argc, + char **argv) + { ++ g_autoptr (WaylandDisplay) display = NULL; ++ struct xdg_toplevel *xdg_toplevel; ++ struct xdg_surface *xdg_surface; ++ struct wl_surface *surface; ++ + display = wayland_display_new (WAYLAND_DISPLAY_CAPABILITY_TEST_DRIVER); + + surface = wl_compositor_create_surface (display->compositor); +@@ -163,9 +165,9 @@ main (int argc, + + xdg_toplevel_set_fullscreen(xdg_toplevel, NULL); + wl_surface_commit (surface); +- wait_for_configure (); ++ wait_for_configure (display); + +- draw_main (FALSE); ++ draw_main (display, surface, FALSE); + wl_surface_commit (surface); + wait_for_effects_completed (display, surface); + +@@ -185,7 +187,7 @@ main (int argc, + wl_surface_commit (surface); + wait_for_view_verified (display, 3); + +- draw_main (TRUE); ++ draw_main (display, surface, TRUE); + wl_surface_set_buffer_transform (surface, WL_OUTPUT_TRANSFORM_90); + wl_surface_commit (surface); + wait_for_view_verified (display, 4); +@@ -202,7 +204,5 @@ main (int argc, + wl_surface_commit (surface); + wait_for_view_verified (display, 7); + +- g_clear_object (&display); +- + return EXIT_SUCCESS; + } +diff --git a/src/tests/wayland-test-clients/dma-buf-scanout.c b/src/tests/wayland-test-clients/dma-buf-scanout.c +index bc499164e..d71763947 100644 +--- a/src/tests/wayland-test-clients/dma-buf-scanout.c ++++ b/src/tests/wayland-test-clients/dma-buf-scanout.c +@@ -55,11 +55,7 @@ typedef enum + WINDOW_STATE_FULLSCREEN, + } WindowState; + +-static WaylandDisplay *display; +- + static struct wl_surface *surface; +-static struct xdg_surface *xdg_surface; +-static struct xdg_toplevel *xdg_toplevel; + + static GList *active_buffers; + +@@ -84,7 +80,7 @@ static const struct wl_buffer_listener buffer_listener = { + }; + + static void +-init_surface (void) ++init_surface (struct xdg_toplevel *xdg_toplevel) + { + xdg_toplevel_set_title (xdg_toplevel, "dma-buf-scanout-test"); + xdg_toplevel_set_fullscreen (xdg_toplevel, NULL); +@@ -92,8 +88,9 @@ init_surface (void) + } + + static void +-draw_main (int width, +- int height) ++draw_main (WaylandDisplay *display, ++ int width, ++ int height) + { + WaylandBuffer *buffer; + DmaBufFormat *format; +@@ -140,9 +137,11 @@ handle_xdg_toplevel_configure (void *user_data, + int32_t height, + struct wl_array *states) + { ++ WaylandDisplay *display; + g_assert (width > 0 || prev_width > 0); + g_assert (height > 0 || prev_width > 0); + ++ display = user_data; + if (width > 0 && height > 0) + { + prev_width = width; +@@ -156,7 +155,7 @@ handle_xdg_toplevel_configure (void *user_data, + + window_state = parse_xdg_toplevel_state (states); + +- draw_main (width, height); ++ draw_main (display, width, height); + } + + static void +@@ -196,6 +195,7 @@ handle_xdg_surface_configure (void *user_data, + struct xdg_surface *xdg_surface, + uint32_t serial) + { ++ WaylandDisplay *display = user_data; + struct wl_callback *frame_callback; + + xdg_surface_ack_configure (xdg_surface, serial); +@@ -223,6 +223,10 @@ int + main (int argc, + char **argv) + { ++ g_autoptr (WaylandDisplay) display = NULL; ++ struct xdg_toplevel *xdg_toplevel; ++ struct xdg_surface *xdg_surface; ++ + display = wayland_display_new (WAYLAND_DISPLAY_CAPABILITY_TEST_DRIVER); + g_signal_connect (display, "sync-event", G_CALLBACK (on_sync_event), NULL); + wl_display_roundtrip (display->display); +@@ -230,11 +234,11 @@ main (int argc, + + surface = wl_compositor_create_surface (display->compositor); + xdg_surface = xdg_wm_base_get_xdg_surface (display->xdg_wm_base, surface); +- xdg_surface_add_listener (xdg_surface, &xdg_surface_listener, NULL); ++ xdg_surface_add_listener (xdg_surface, &xdg_surface_listener, display); + xdg_toplevel = xdg_surface_get_toplevel (xdg_surface); +- xdg_toplevel_add_listener (xdg_toplevel, &xdg_toplevel_listener, NULL); ++ xdg_toplevel_add_listener (xdg_toplevel, &xdg_toplevel_listener, display); + +- init_surface (); ++ init_surface (xdg_toplevel); + + running = TRUE; + while (running) +@@ -244,7 +248,6 @@ main (int argc, + } + + g_list_free_full (active_buffers, (GDestroyNotify) g_object_unref); +- g_object_unref (display); + + return EXIT_SUCCESS; + } +diff --git a/src/tests/wayland-test-clients/fractional-scale.c b/src/tests/wayland-test-clients/fractional-scale.c +index 4ed55ebe3..f62aa2033 100644 +--- a/src/tests/wayland-test-clients/fractional-scale.c ++++ b/src/tests/wayland-test-clients/fractional-scale.c +@@ -23,12 +23,8 @@ + + #include "wayland-test-client-utils.h" + +-static WaylandDisplay *display; + static struct wl_surface *surface; +-static struct xdg_surface *xdg_surface; +-static struct xdg_toplevel *xdg_toplevel; + static struct wp_viewport *viewport; +-static struct wp_fractional_scale_v1 *fractional_scale_obj; + + static gboolean running; + static gboolean waiting_for_configure; +@@ -43,6 +39,8 @@ handle_frame_callback (void *data, + struct wl_callback *callback, + uint32_t time) + { ++ WaylandDisplay *display = data; ++ + wl_callback_destroy (callback); + test_driver_sync_point (display->test_driver, sync_point++, NULL); + } +@@ -52,7 +50,7 @@ static const struct wl_callback_listener frame_listener = { + }; + + static void +-maybe_redraw (void) ++maybe_redraw (WaylandDisplay *display) + { + struct wl_callback *callback; + uint32_t buffer_width; +@@ -72,7 +70,7 @@ maybe_redraw (void) + wp_viewport_set_destination (viewport, logical_width, logical_height); + + callback = wl_surface_frame (surface); +- wl_callback_add_listener (callback, &frame_listener, NULL); ++ wl_callback_add_listener (callback, &frame_listener, display); + + wl_surface_commit (surface); + +@@ -112,10 +110,11 @@ handle_xdg_surface_configure (void *data, + struct xdg_surface *xdg_surface, + uint32_t serial) + { ++ WaylandDisplay *display = data; + xdg_surface_ack_configure (xdg_surface, serial); + waiting_for_configure = FALSE; + +- maybe_redraw (); ++ maybe_redraw (display); + } + + static const struct xdg_surface_listener xdg_surface_listener = { +@@ -126,6 +125,7 @@ static void handle_preferred_scale (void *data, + struct wp_fractional_scale_v1 *fractional_scale_obj, + uint32_t wire_scale) + { ++ WaylandDisplay *display = data; + float new_fractional_buffer_scale; + + new_fractional_buffer_scale = wire_scale / 120.0; +@@ -136,7 +136,7 @@ static void handle_preferred_scale (void *data, + + fractional_buffer_scale = new_fractional_buffer_scale; + waiting_for_scale = FALSE; +- maybe_redraw (); ++ maybe_redraw (display); + } + + static const struct wp_fractional_scale_v1_listener fractional_scale_listener = { +@@ -147,11 +147,16 @@ int + main (int argc, + char **argv) + { ++ g_autoptr (WaylandDisplay) display = NULL; ++ struct xdg_toplevel *xdg_toplevel; ++ struct xdg_surface *xdg_surface; ++ struct wp_fractional_scale_v1 *fractional_scale_obj; ++ + display = wayland_display_new (WAYLAND_DISPLAY_CAPABILITY_TEST_DRIVER); + + surface = wl_compositor_create_surface (display->compositor); + xdg_surface = xdg_wm_base_get_xdg_surface (display->xdg_wm_base, surface); +- xdg_surface_add_listener (xdg_surface, &xdg_surface_listener, NULL); ++ xdg_surface_add_listener (xdg_surface, &xdg_surface_listener, display); + xdg_toplevel = xdg_surface_get_toplevel (xdg_surface); + xdg_toplevel_add_listener (xdg_toplevel, &xdg_toplevel_listener, NULL); + xdg_toplevel_set_title (xdg_toplevel, "fractional-scale"); +@@ -163,7 +168,7 @@ main (int argc, + surface); + wp_fractional_scale_v1_add_listener (fractional_scale_obj, + &fractional_scale_listener, +- NULL); ++ display); + + wl_surface_commit (surface); + +diff --git a/src/tests/wayland-test-clients/fullscreen.c b/src/tests/wayland-test-clients/fullscreen.c +index 9690f6e85..804be4eb2 100644 +--- a/src/tests/wayland-test-clients/fullscreen.c ++++ b/src/tests/wayland-test-clients/fullscreen.c +@@ -24,11 +24,7 @@ + + #include "wayland-test-client-utils.h" + +-static WaylandDisplay *display; +- + static struct wl_surface *surface; +-static struct xdg_surface *xdg_surface; +-static struct xdg_toplevel *xdg_toplevel; + + static gboolean running; + +@@ -58,6 +54,8 @@ handle_xdg_surface_configure (void *data, + struct xdg_surface *xdg_surface, + uint32_t serial) + { ++ WaylandDisplay *display = data; ++ + draw_surface (display, surface, 10, 10, 0x1f109f20); + xdg_surface_ack_configure (xdg_surface, serial); + wl_surface_commit (surface); +@@ -81,12 +79,16 @@ int + main (int argc, + char **argv) + { ++ g_autoptr (WaylandDisplay) display = NULL; ++ struct xdg_toplevel *xdg_toplevel; ++ struct xdg_surface *xdg_surface; ++ + display = wayland_display_new (WAYLAND_DISPLAY_CAPABILITY_TEST_DRIVER); + g_signal_connect (display, "sync-event", G_CALLBACK (on_sync_event), NULL); + + surface = wl_compositor_create_surface (display->compositor); + xdg_surface = xdg_wm_base_get_xdg_surface (display->xdg_wm_base, surface); +- xdg_surface_add_listener (xdg_surface, &xdg_surface_listener, NULL); ++ xdg_surface_add_listener (xdg_surface, &xdg_surface_listener, display); + xdg_toplevel = xdg_surface_get_toplevel (xdg_surface); + xdg_toplevel_add_listener (xdg_toplevel, &xdg_toplevel_listener, NULL); + xdg_toplevel_set_title (xdg_toplevel, "fullscreen"); +diff --git a/src/tests/wayland-test-clients/idle-inhibit.c b/src/tests/wayland-test-clients/idle-inhibit.c +index 23079e879..953db2b92 100644 +--- a/src/tests/wayland-test-clients/idle-inhibit.c ++++ b/src/tests/wayland-test-clients/idle-inhibit.c +@@ -24,7 +24,6 @@ + + #include "idle-inhibit-unstable-v1-client-protocol.h" + +-static WaylandDisplay *display; + struct zwp_idle_inhibit_manager_v1 *idle_inhibit_manager; + + static void +@@ -59,7 +58,8 @@ main (int argc, + char **argv) + { + struct wl_registry *registry; +- WaylandSurface *surface; ++ g_autoptr (WaylandSurface) surface; ++ g_autoptr (WaylandDisplay) display = NULL; + struct zwp_idle_inhibitor_v1 *inhibitor; + + display = wayland_display_new (WAYLAND_DISPLAY_CAPABILITY_TEST_DRIVER); +diff --git a/src/tests/wayland-test-clients/invalid-subsurfaces.c b/src/tests/wayland-test-clients/invalid-subsurfaces.c +index 103456e93..2cf0d4ecf 100644 +--- a/src/tests/wayland-test-clients/invalid-subsurfaces.c ++++ b/src/tests/wayland-test-clients/invalid-subsurfaces.c +@@ -24,33 +24,16 @@ + + #include "wayland-test-client-utils.h" + +-static WaylandDisplay *display; +- +-static void +-connect_to_display (void) +-{ +- g_assert_null (display); +- +- display = wayland_display_new (WAYLAND_DISPLAY_CAPABILITY_NONE); +- g_assert_nonnull (display); +-} +- +-static void +-clean_up_display (void) +-{ +- g_clear_object (&display); +-} +- + static void + test_circular_subsurfaces1 (void) + { ++ g_autoptr (WaylandDisplay) display = NULL; + struct wl_surface *surface1; + struct wl_subsurface *subsurface1; + struct wl_surface *surface2; + struct wl_subsurface *subsurface2; + +- connect_to_display (); +- ++ display = wayland_display_new (WAYLAND_DISPLAY_CAPABILITY_NONE); + surface1 = wl_compositor_create_surface (display->compositor); + surface2 = wl_compositor_create_surface (display->compositor); + g_assert_nonnull (surface1); +@@ -66,13 +49,12 @@ test_circular_subsurfaces1 (void) + g_assert_nonnull (subsurface2); + + g_assert_cmpint (wl_display_roundtrip (display->display), ==, -1); +- +- clean_up_display (); + } + + static void + test_circular_subsurfaces2 (void) + { ++ g_autoptr (WaylandDisplay) display = NULL; + struct wl_surface *surface1; + struct wl_subsurface *subsurface1; + struct wl_surface *surface2; +@@ -80,7 +62,7 @@ test_circular_subsurfaces2 (void) + struct wl_surface *surface3; + struct wl_subsurface *subsurface3; + +- connect_to_display (); ++ display = wayland_display_new (WAYLAND_DISPLAY_CAPABILITY_NONE); + + surface1 = wl_compositor_create_surface (display->compositor); + surface2 = wl_compositor_create_surface (display->compositor); +@@ -103,8 +85,6 @@ test_circular_subsurfaces2 (void) + g_assert_nonnull (subsurface3); + + g_assert_cmpint (wl_display_roundtrip (display->display), ==, -1); +- +- clean_up_display (); + } + + int +diff --git a/src/tests/wayland-test-clients/invalid-xdg-shell-actions.c b/src/tests/wayland-test-clients/invalid-xdg-shell-actions.c +index bd75dc995..4ffda8d5a 100644 +--- a/src/tests/wayland-test-clients/invalid-xdg-shell-actions.c ++++ b/src/tests/wayland-test-clients/invalid-xdg-shell-actions.c +@@ -22,23 +22,19 @@ + + #include "wayland-test-client-utils.h" + +-static WaylandDisplay *display; +- + static struct wl_surface *surface; +-static struct xdg_surface *xdg_surface; +-static struct xdg_toplevel *xdg_toplevel; + + static gboolean running; + + static void +-init_surface (void) ++init_surface (struct xdg_toplevel *xdg_toplevel) + { + xdg_toplevel_set_title (xdg_toplevel, "bogus window geometry"); + wl_surface_commit (surface); + } + + static void +-draw_main (void) ++draw_main (WaylandDisplay *display) + { + draw_surface (display, surface, 700, 500, 0xff00ff00); + } +@@ -69,13 +65,14 @@ handle_xdg_surface_configure (void *data, + struct xdg_surface *xdg_surface, + uint32_t serial) + { ++ WaylandDisplay *display = data; + static gboolean sent_invalid_once = FALSE; + + if (sent_invalid_once) + return; + + xdg_surface_set_window_geometry (xdg_surface, 0, 0, 0, 0); +- draw_main (); ++ draw_main (display); + wl_surface_commit (surface); + + sent_invalid_once = TRUE; +@@ -91,15 +88,19 @@ static const struct xdg_surface_listener xdg_surface_listener = { + static void + test_empty_window_geometry (void) + { ++ g_autoptr (WaylandDisplay) display = NULL; ++ struct xdg_toplevel *xdg_toplevel; ++ struct xdg_surface *xdg_surface; ++ + display = wayland_display_new (WAYLAND_DISPLAY_CAPABILITY_NONE); + + surface = wl_compositor_create_surface (display->compositor); + xdg_surface = xdg_wm_base_get_xdg_surface (display->xdg_wm_base, surface); +- xdg_surface_add_listener (xdg_surface, &xdg_surface_listener, NULL); ++ xdg_surface_add_listener (xdg_surface, &xdg_surface_listener, display); + xdg_toplevel = xdg_surface_get_toplevel (xdg_surface); + xdg_toplevel_add_listener (xdg_toplevel, &xdg_toplevel_listener, NULL); + +- init_surface (); ++ init_surface (xdg_toplevel); + + running = TRUE; + while (running) +@@ -110,7 +111,6 @@ test_empty_window_geometry (void) + + g_clear_pointer (&xdg_toplevel, xdg_toplevel_destroy); + g_clear_pointer (&xdg_surface, xdg_surface_destroy); +- g_clear_object (&display); + } + + int +diff --git a/src/tests/wayland-test-clients/kms-cursor-hotplug-helper.c b/src/tests/wayland-test-clients/kms-cursor-hotplug-helper.c +index 0ddaa4a04..b46a88563 100644 +--- a/src/tests/wayland-test-clients/kms-cursor-hotplug-helper.c ++++ b/src/tests/wayland-test-clients/kms-cursor-hotplug-helper.c +@@ -23,33 +23,28 @@ + + #include "wayland-test-client-utils.h" + +-static WaylandDisplay *display; +- +-static struct wl_registry *wl_registry; + static struct wl_seat *wl_seat; + static struct wl_pointer *wl_pointer; + static uint32_t enter_serial; + + static struct wl_surface *surface; +-static struct xdg_surface *xdg_surface; +-static struct xdg_toplevel *xdg_toplevel; + struct wl_surface *cursor_surface; +-struct wl_cursor_theme *cursor_theme; + struct wl_cursor *cursor; + struct wl_cursor *cursor2; + + static gboolean running; + + static void +-init_surface (void) ++init_surface (struct xdg_toplevel *xdg_toplevel) + { + xdg_toplevel_set_title (xdg_toplevel, "kms-cursor-hotplug-helper"); + wl_surface_commit (surface); + } + + static void +-draw_main (int width, +- int height) ++draw_main (WaylandDisplay *display, ++ int width, ++ int height) + { + draw_surface (display, surface, width, height, 0xff00ff00); + } +@@ -89,7 +84,9 @@ handle_xdg_surface_configure (void *data, + struct xdg_surface *xdg_surface, + uint32_t serial) + { +- draw_main (100, 100); ++ WaylandDisplay *display = data; ++ ++ draw_main (display, 100, 100); + xdg_surface_ack_configure (xdg_surface, serial); + wl_surface_commit (surface); + } +@@ -251,6 +248,12 @@ int + main (int argc, + char **argv) + { ++ g_autoptr (WaylandDisplay) display = NULL; ++ struct wl_registry *wl_registry; ++ struct xdg_toplevel *xdg_toplevel; ++ struct xdg_surface *xdg_surface; ++ struct wl_cursor_theme *cursor_theme; ++ + display = wayland_display_new (WAYLAND_DISPLAY_CAPABILITY_TEST_DRIVER); + wl_registry = wl_display_get_registry (display->display); + wl_registry_add_listener (wl_registry, ®istry_listener, display); +@@ -260,7 +263,7 @@ main (int argc, + + surface = wl_compositor_create_surface (display->compositor); + xdg_surface = xdg_wm_base_get_xdg_surface (display->xdg_wm_base, surface); +- xdg_surface_add_listener (xdg_surface, &xdg_surface_listener, NULL); ++ xdg_surface_add_listener (xdg_surface, &xdg_surface_listener, display); + xdg_toplevel = xdg_surface_get_toplevel (xdg_surface); + xdg_toplevel_add_listener (xdg_toplevel, &xdg_toplevel_listener, NULL); + +@@ -271,7 +274,7 @@ main (int argc, + g_assert_nonnull (cursor); + g_assert_nonnull (cursor2); + +- init_surface (); ++ init_surface (xdg_toplevel); + wl_surface_commit (surface); + + running = TRUE; +diff --git a/src/tests/wayland-test-clients/single-pixel-buffer.c b/src/tests/wayland-test-clients/single-pixel-buffer.c +index ed98939a3..d350e4d8e 100644 +--- a/src/tests/wayland-test-clients/single-pixel-buffer.c ++++ b/src/tests/wayland-test-clients/single-pixel-buffer.c +@@ -22,13 +22,7 @@ + + #include "wayland-test-client-utils.h" + +-static WaylandDisplay *display; +-static struct wl_surface *surface; +-static struct xdg_surface *xdg_surface; +-static struct xdg_toplevel *xdg_toplevel; + static struct wl_buffer *buffer; +-static struct wl_surface *subsurface_surface; +-static struct wl_subsurface *subsurface; + + static gboolean waiting_for_configure = FALSE; + static gboolean fullscreen = 0; +@@ -91,7 +85,7 @@ static const struct xdg_surface_listener xdg_surface_listener = { + }; + + static void +-wait_for_configure (void) ++wait_for_configure (WaylandDisplay *display) + { + waiting_for_configure = TRUE; + while (waiting_for_configure || window_width == 0) +@@ -114,7 +108,7 @@ static const struct wl_buffer_listener buffer_listener = { + }; + + static void +-wait_for_buffer_released (void) ++wait_for_buffer_released (WaylandDisplay *display) + { + while (buffer) + { +@@ -127,8 +121,14 @@ int + main (int argc, + char **argv) + { ++ g_autoptr (WaylandDisplay) display = NULL; + struct wp_viewport *viewport; + struct wp_viewport *subsurface_viewport; ++ struct xdg_toplevel *xdg_toplevel; ++ struct xdg_surface *xdg_surface; ++ struct wl_surface *subsurface_surface; ++ struct wl_subsurface *subsurface; ++ struct wl_surface *surface; + + display = wayland_display_new (WAYLAND_DISPLAY_CAPABILITY_TEST_DRIVER); + +@@ -140,7 +140,7 @@ main (int argc, + + xdg_toplevel_set_fullscreen (xdg_toplevel, NULL); + wl_surface_commit (surface); +- wait_for_configure (); ++ wait_for_configure (display); + + viewport = wp_viewporter_get_viewport (display->viewporter, surface); + wp_viewport_set_destination (viewport, window_width, window_height); +@@ -157,7 +157,7 @@ main (int argc, + wait_for_effects_completed (display, surface); + wait_for_view_verified (display, 0); + +- wait_for_buffer_released (); ++ wait_for_buffer_released (display); + + subsurface_surface = wl_compositor_create_surface (display->compositor); + subsurface = wl_subcompositor_get_subsurface (display->subcompositor, +@@ -184,7 +184,7 @@ main (int argc, + wl_surface_commit (subsurface_surface); + wait_for_view_verified (display, 1); + +- wait_for_buffer_released (); ++ wait_for_buffer_released (display); + + buffer = + wp_single_pixel_buffer_manager_v1_create_u32_rgba_buffer (display->single_pixel_mgr, +@@ -197,7 +197,7 @@ main (int argc, + wl_surface_commit (subsurface_surface); + wait_for_view_verified (display, 0); + +- wait_for_buffer_released (); ++ wait_for_buffer_released (display); + + buffer = + wp_single_pixel_buffer_manager_v1_create_u32_rgba_buffer (display->single_pixel_mgr, +@@ -210,7 +210,7 @@ main (int argc, + wl_surface_commit (subsurface_surface); + wait_for_view_verified (display, 2); + +- wait_for_buffer_released (); ++ wait_for_buffer_released (display); + + buffer = + wp_single_pixel_buffer_manager_v1_create_u32_rgba_buffer (display->single_pixel_mgr, +@@ -223,7 +223,7 @@ main (int argc, + wl_surface_commit (subsurface_surface); + wait_for_view_verified (display, 3); + +- wait_for_buffer_released (); ++ wait_for_buffer_released (display); + + buffer = + wp_single_pixel_buffer_manager_v1_create_u32_rgba_buffer (display->single_pixel_mgr, +@@ -236,7 +236,7 @@ main (int argc, + wl_surface_commit (subsurface_surface); + wait_for_view_verified (display, 4); + +- wait_for_buffer_released (); ++ wait_for_buffer_released (display); + + buffer = + wp_single_pixel_buffer_manager_v1_create_u32_rgba_buffer (display->single_pixel_mgr, +@@ -249,7 +249,7 @@ main (int argc, + wl_surface_commit (subsurface_surface); + wait_for_view_verified (display, 5); + +- wait_for_buffer_released (); ++ wait_for_buffer_released (display); + + buffer = + wp_single_pixel_buffer_manager_v1_create_u32_rgba_buffer (display->single_pixel_mgr, +@@ -262,9 +262,43 @@ main (int argc, + wl_surface_commit (subsurface_surface); + wait_for_view_verified (display, 6); + +- wait_for_buffer_released (); ++ wait_for_buffer_released (display); + +- g_clear_object (&display); ++ /* Test reuse */ ++ ++ buffer = ++ wp_single_pixel_buffer_manager_v1_create_u32_rgba_buffer (display->single_pixel_mgr, ++ 0x70707070, ++ 0x00000000, ++ 0x70707070, ++ 0x70707070); ++ wl_surface_attach (subsurface_surface, buffer, 0, 0); ++ wl_surface_commit (subsurface_surface); ++ wait_for_view_verified (display, 7); ++ ++ wl_subsurface_destroy (subsurface); ++ wl_surface_destroy (subsurface_surface); ++ ++ subsurface_surface = wl_compositor_create_surface (display->compositor); ++ subsurface = wl_subcompositor_get_subsurface (display->subcompositor, ++ subsurface_surface, ++ surface); ++ wl_subsurface_set_desync (subsurface); ++ wl_subsurface_set_position (subsurface, 30, 30); ++ wl_surface_commit (surface); ++ ++ subsurface_viewport = wp_viewporter_get_viewport (display->viewporter, ++ subsurface_surface); ++ wp_viewport_set_destination (subsurface_viewport, ++ window_width - 60, ++ window_height - 60); ++ ++ wl_buffer_add_listener (buffer, &buffer_listener, NULL); ++ wl_surface_attach (subsurface_surface, buffer, 0, 0); ++ wl_surface_commit (subsurface_surface); ++ wait_for_view_verified (display, 8); ++ ++ wait_for_buffer_released (display); + + return EXIT_SUCCESS; + } +diff --git a/src/tests/wayland-test-clients/subsurface-corner-cases.c b/src/tests/wayland-test-clients/subsurface-corner-cases.c +index c3b239b2c..449bcb83d 100644 +--- a/src/tests/wayland-test-clients/subsurface-corner-cases.c ++++ b/src/tests/wayland-test-clients/subsurface-corner-cases.c +@@ -22,12 +22,6 @@ + + #include "wayland-test-client-utils.h" + +-static WaylandDisplay *display; +-static struct wl_surface *toplevel_surface, *child_surface, *grandchild_surface; +-static struct wl_subsurface *child, *grandchild; +-static struct xdg_surface *xdg_surface; +-static struct xdg_toplevel *xdg_toplevel; +- + static gboolean waiting_for_configure = FALSE; + static gboolean fullscreen = 0; + static uint32_t window_width = 0; +@@ -75,7 +69,8 @@ static const struct xdg_toplevel_listener xdg_toplevel_listener = { + }; + + static void +-draw_toplevel (void) ++draw_toplevel (WaylandDisplay *display, ++ struct wl_surface *toplevel_surface) + { + draw_surface (display, toplevel_surface, + window_width, window_height, +@@ -97,7 +92,8 @@ static const struct xdg_surface_listener xdg_surface_listener = { + }; + + static void +-draw_child (void) ++draw_child (WaylandDisplay *display, ++ struct wl_surface *child_surface) + { + draw_surface (display, child_surface, + window_width / 2, window_height / 2, +@@ -105,7 +101,8 @@ draw_child (void) + } + + static void +-draw_grandchild (void) ++draw_grandchild (WaylandDisplay *display, ++ struct wl_surface *grandchild_surface) + { + draw_surface (display, grandchild_surface, + window_width / 2, window_height / 2, +@@ -113,7 +110,7 @@ draw_grandchild (void) + } + + static void +-wait_for_configure (void) ++wait_for_configure (WaylandDisplay *display) + { + waiting_for_configure = TRUE; + while (waiting_for_configure || window_width == 0) +@@ -127,6 +124,12 @@ int + main (int argc, + char **argv) + { ++ g_autoptr (WaylandDisplay) display = NULL; ++ struct xdg_toplevel *xdg_toplevel; ++ struct xdg_surface *xdg_surface; ++ struct wl_surface *toplevel_surface, *child_surface, *grandchild_surface; ++ struct wl_subsurface *child, *grandchild; ++ + display = wayland_display_new (WAYLAND_DISPLAY_CAPABILITY_TEST_DRIVER); + + toplevel_surface = wl_compositor_create_surface (display->compositor); +@@ -138,9 +141,9 @@ main (int argc, + + xdg_toplevel_set_fullscreen (xdg_toplevel, NULL); + wl_surface_commit (toplevel_surface); +- wait_for_configure (); ++ wait_for_configure (display); + +- draw_toplevel (); ++ draw_toplevel (display, toplevel_surface); + wl_surface_commit (toplevel_surface); + wait_for_effects_completed (display, toplevel_surface); + +@@ -148,7 +151,7 @@ main (int argc, + child = wl_subcompositor_get_subsurface (display->subcompositor, + child_surface, + toplevel_surface); +- draw_child (); ++ draw_child (display, child_surface); + wl_surface_commit (child_surface); + /* No toplevel commit → sub-surface must not be mapped yet */ + wait_for_view_verified (display, 0); +@@ -174,7 +177,7 @@ main (int argc, + /* Toplevel commit → sub-surface must be unmapped */ + wait_for_view_verified (display, 5); + +- draw_child (); ++ draw_child (display, child_surface); + wl_surface_commit (child_surface); + wl_subsurface_set_desync (child); + wl_surface_attach (child_surface, NULL, 0, 0); +@@ -182,7 +185,7 @@ main (int argc, + /* Desync sub-surface must have been unmapped */ + wait_for_view_verified (display, 6); + +- draw_child (); ++ draw_child (display, child_surface); + wl_surface_commit (child_surface); + wl_subsurface_set_sync (child); + wl_subsurface_destroy (child); +@@ -192,7 +195,7 @@ main (int argc, + child = wl_subcompositor_get_subsurface (display->subcompositor, + child_surface, + toplevel_surface); +- draw_child (); ++ draw_child (display, child_surface); + wl_surface_commit (child_surface); + /* No toplevel commit → sub-surface must not be mapped yet */ + wait_for_view_verified (display, 8); +@@ -209,7 +212,7 @@ main (int argc, + child = wl_subcompositor_get_subsurface (display->subcompositor, + child_surface, + toplevel_surface); +- draw_child (); ++ draw_child (display, child_surface); + wl_surface_commit (child_surface); + wl_surface_commit (toplevel_surface); + /* New sub-surface → placement below toplevel must not have taken effect */ +@@ -219,7 +222,7 @@ main (int argc, + grandchild = wl_subcompositor_get_subsurface (display->subcompositor, + grandchild_surface, + child_surface); +- draw_grandchild (); ++ draw_grandchild (display, grandchild_surface); + wl_subsurface_set_position (grandchild, window_width / 4, window_height / 4); + wl_surface_commit (grandchild_surface); + wl_surface_commit (child_surface); +@@ -236,7 +239,7 @@ main (int argc, + grandchild = wl_subcompositor_get_subsurface (display->subcompositor, + grandchild_surface, + child_surface); +- draw_grandchild (); ++ draw_grandchild (display, grandchild_surface); + wl_subsurface_set_position (grandchild, window_width / 4, window_height / 4); + wl_surface_commit (grandchild_surface); + wl_surface_commit (child_surface); +@@ -244,7 +247,5 @@ main (int argc, + /* New grandchild must be placed above its parent */ + wait_for_view_verified (display, 14); + +- g_clear_object (&display); +- + return EXIT_SUCCESS; + } +diff --git a/src/tests/wayland-test-clients/subsurface-parent-unmapped.c b/src/tests/wayland-test-clients/subsurface-parent-unmapped.c +index 7fc83b07d..40c72ea33 100644 +--- a/src/tests/wayland-test-clients/subsurface-parent-unmapped.c ++++ b/src/tests/wayland-test-clients/subsurface-parent-unmapped.c +@@ -23,36 +23,32 @@ + + #include "wayland-test-client-utils.h" + +-static WaylandDisplay *display; +-static struct wl_registry *registry; + static struct wl_seat *seat; + static struct wl_pointer *pointer; + + static struct wl_surface *toplevel_surface; + static struct xdg_surface *toplevel_xdg_surface; +-static struct xdg_toplevel *xdg_toplevel; + + static struct wl_surface *popup_surface; + static struct xdg_surface *popup_xdg_surface; + static struct xdg_popup *xdg_popup; + + static struct wl_surface *subsurface_surface; +-static struct wl_subsurface *subsurface; + + static void +-draw_main (void) ++draw_main (WaylandDisplay *display) + { + draw_surface (display, toplevel_surface, 200, 200, 0xff00ffff); + } + + static void +-draw_popup (void) ++draw_popup (WaylandDisplay *display) + { + draw_surface (display, popup_surface, 100, 100, 0xff005500); + } + + static void +-draw_subsurface (void) ++draw_subsurface (WaylandDisplay *display) + { + draw_surface (display, subsurface_surface, 100, 50, 0xff001f00); + } +@@ -83,8 +79,10 @@ handle_toplevel_xdg_surface_configure (void *data, + struct xdg_surface *xdg_surface, + uint32_t serial) + { ++ WaylandDisplay *display = data; ++ + xdg_surface_ack_configure (xdg_surface, serial); +- draw_main (); ++ draw_main (display); + wl_surface_commit (toplevel_surface); + wl_display_flush (display->display); + } +@@ -125,6 +123,8 @@ handle_popup_frame_callback (void *data, + struct wl_callback *callback, + uint32_t time) + { ++ WaylandDisplay *display = data; ++ + wl_callback_destroy (callback); + test_driver_sync_point (display->test_driver, 0, popup_surface); + } +@@ -138,16 +138,17 @@ handle_popup_xdg_surface_configure (void *data, + struct xdg_surface *xdg_surface, + uint32_t serial) + { ++ WaylandDisplay *display = data; + struct wl_callback *frame_callback; + +- draw_popup (); ++ draw_popup (display); + +- draw_subsurface (); ++ draw_subsurface (display); + wl_surface_commit (subsurface_surface); + + xdg_surface_ack_configure (xdg_surface, serial); + frame_callback = wl_surface_frame (popup_surface); +- wl_callback_add_listener (frame_callback, &frame_listener, NULL); ++ wl_callback_add_listener (frame_callback, &frame_listener, display); + wl_surface_commit (popup_surface); + wl_display_flush (display->display); + } +@@ -164,6 +165,7 @@ pointer_handle_button (void *data, + uint32_t button, + uint32_t state) + { ++ WaylandDisplay *display = data; + struct xdg_positioner *positioner; + static int click_count = 0; + +@@ -174,7 +176,7 @@ pointer_handle_button (void *data, + popup_xdg_surface = xdg_wm_base_get_xdg_surface (display->xdg_wm_base, + popup_surface); + xdg_surface_add_listener (popup_xdg_surface, +- &popup_xdg_surface_listener, NULL); ++ &popup_xdg_surface_listener, display); + positioner = xdg_wm_base_create_positioner (display->xdg_wm_base); + xdg_positioner_set_size (positioner, 100, 100); + xdg_positioner_set_anchor_rect (positioner, 0, 0, 1, 1); +@@ -219,10 +221,12 @@ seat_handle_capabilities (void *data, + struct wl_seat *wl_seat, + enum wl_seat_capability caps) + { ++ WaylandDisplay *display = data; ++ + if (caps & WL_SEAT_CAPABILITY_POINTER) + { + pointer = wl_seat_get_pointer (wl_seat); +- wl_pointer_add_listener (pointer, &pointer_listener, NULL); ++ wl_pointer_add_listener (pointer, &pointer_listener, display); + } + } + +@@ -263,10 +267,12 @@ handle_registry_global (void *data, + const char *interface, + uint32_t version) + { ++ WaylandDisplay *display = data; ++ + if (strcmp (interface, "wl_seat") == 0) + { + seat = wl_registry_bind (registry, id, &wl_seat_interface, 1); +- wl_seat_add_listener (seat, &seat_listener, NULL); ++ wl_seat_add_listener (seat, &seat_listener, display); + } + } + +@@ -286,12 +292,17 @@ int + main (int argc, + char **argv) + { ++ g_autoptr (WaylandDisplay) display = NULL; ++ struct wl_registry *registry; ++ struct xdg_toplevel *xdg_toplevel; ++ struct wl_subsurface *subsurface; ++ + display = wayland_display_new (WAYLAND_DISPLAY_CAPABILITY_TEST_DRIVER); + + g_signal_connect (display, "sync-event", G_CALLBACK (on_sync_event), NULL); + + registry = wl_display_get_registry (display->display); +- wl_registry_add_listener (registry, ®istry_listener, NULL); ++ wl_registry_add_listener (registry, ®istry_listener, display); + wl_display_roundtrip (display->display); + wl_display_roundtrip (display->display); + +@@ -311,7 +322,7 @@ main (int argc, + toplevel_xdg_surface = xdg_wm_base_get_xdg_surface (display->xdg_wm_base, + toplevel_surface); + xdg_surface_add_listener (toplevel_xdg_surface, +- &toplevel_xdg_surface_listener, NULL); ++ &toplevel_xdg_surface_listener, display); + xdg_toplevel = xdg_surface_get_toplevel (toplevel_xdg_surface); + xdg_toplevel_add_listener (xdg_toplevel, &xdg_toplevel_listener, NULL); + xdg_toplevel_set_title (xdg_toplevel, "subsurface-parent-unmapped"); +@@ -331,7 +342,5 @@ main (int argc, + return EXIT_FAILURE; + } + +- g_clear_object (&display); +- + return EXIT_SUCCESS; + } +diff --git a/src/tests/wayland-test-clients/subsurface-remap-toplevel.c b/src/tests/wayland-test-clients/subsurface-remap-toplevel.c +index e23aacabb..c1bb90ea5 100644 +--- a/src/tests/wayland-test-clients/subsurface-remap-toplevel.c ++++ b/src/tests/wayland-test-clients/subsurface-remap-toplevel.c +@@ -32,13 +32,9 @@ typedef enum _State + STATE_WAIT_FOR_FRAME_2 + } State; + +-static WaylandDisplay *display; +- + static struct wl_surface *surface; +-static struct xdg_surface *xdg_surface; + static struct xdg_toplevel *xdg_toplevel; + +-static struct wl_surface *subsurface_surface; + static struct wl_subsurface *subsurface; + + static struct wl_callback *frame_callback; +@@ -72,7 +68,7 @@ static const struct wl_callback_listener actor_destroy_listener = { + }; + + static void +-reset_surface (void) ++reset_surface (WaylandDisplay *display) + { + struct wl_callback *callback; + +@@ -86,13 +82,14 @@ reset_surface (void) + } + + static void +-draw_main (void) ++draw_main (WaylandDisplay *display) + { + draw_surface (display, surface, 700, 500, 0xff00ff00); + } + + static void +-draw_subsurface (void) ++draw_subsurface (WaylandDisplay *display, ++ struct wl_surface *subsurface_surface) + { + draw_surface (display, subsurface_surface, 500, 300, 0xff007f00); + } +@@ -123,10 +120,11 @@ handle_frame_callback (void *data, + struct wl_callback *callback, + uint32_t time) + { ++ WaylandDisplay *display = data; + switch (state) + { + case STATE_WAIT_FOR_FRAME_1: +- reset_surface (); ++ reset_surface (display); + break; + case STATE_WAIT_FOR_FRAME_2: + exit (EXIT_SUCCESS); +@@ -150,16 +148,17 @@ handle_xdg_surface_configure (void *data, + struct xdg_surface *xdg_surface, + uint32_t serial) + { ++ WaylandDisplay *display = data; + switch (state) + { + case STATE_INIT: + g_assert_not_reached (); + case STATE_WAIT_FOR_CONFIGURE_1: +- draw_main (); ++ draw_main (display); + state = STATE_WAIT_FOR_FRAME_1; + break; + case STATE_WAIT_FOR_CONFIGURE_2: +- draw_main (); ++ draw_main (display); + state = STATE_WAIT_FOR_FRAME_2; + break; + case STATE_WAIT_FOR_ACTOR_DESTROYED: +@@ -172,7 +171,7 @@ handle_xdg_surface_configure (void *data, + + xdg_surface_ack_configure (xdg_surface, serial); + frame_callback = wl_surface_frame (surface); +- wl_callback_add_listener (frame_callback, &frame_listener, NULL); ++ wl_callback_add_listener (frame_callback, &frame_listener, display); + wl_surface_commit (surface); + wl_display_flush (display->display); + } +@@ -185,11 +184,15 @@ int + main (int argc, + char **argv) + { ++ g_autoptr (WaylandDisplay) display = NULL; ++ struct xdg_surface *xdg_surface; ++ struct wl_surface *subsurface_surface; ++ + display = wayland_display_new (WAYLAND_DISPLAY_CAPABILITY_TEST_DRIVER); + + surface = wl_compositor_create_surface (display->compositor); + xdg_surface = xdg_wm_base_get_xdg_surface (display->xdg_wm_base, surface); +- xdg_surface_add_listener (xdg_surface, &xdg_surface_listener, NULL); ++ xdg_surface_add_listener (xdg_surface, &xdg_surface_listener, display); + xdg_toplevel = xdg_surface_get_toplevel (xdg_surface); + xdg_toplevel_add_listener (xdg_toplevel, &xdg_toplevel_listener, NULL); + +@@ -198,7 +201,7 @@ main (int argc, + subsurface_surface, + surface); + wl_subsurface_set_position (subsurface, 100, 100); +- draw_subsurface (); ++ draw_subsurface (display, subsurface_surface); + wl_surface_commit (subsurface_surface); + + init_surface (); +@@ -211,7 +214,5 @@ main (int argc, + return EXIT_FAILURE; + } + +- g_clear_object (&display); +- + return EXIT_SUCCESS; + } +diff --git a/src/tests/wayland-test-clients/subsurface-reparenting.c b/src/tests/wayland-test-clients/subsurface-reparenting.c +index f537e0727..7a4c99577 100644 +--- a/src/tests/wayland-test-clients/subsurface-reparenting.c ++++ b/src/tests/wayland-test-clients/subsurface-reparenting.c +@@ -34,8 +34,6 @@ typedef enum _State + STATE_WAIT_FOR_FRAME_3 + } State; + +-static WaylandDisplay *display; +- + static struct wl_surface *surface; + static struct xdg_surface *xdg_surface; + static struct xdg_toplevel *xdg_toplevel; +@@ -47,16 +45,16 @@ static struct wl_callback *frame_callback; + + static State state; + +-static void init_surfaces (void); ++static void init_surfaces (WaylandDisplay *display); + + static void +-draw_main (void) ++draw_main (WaylandDisplay *display) + { + draw_surface (display, surface, 700, 500, 0xff00ff00); + } + + static void +-draw_subsurface (void) ++draw_subsurface (WaylandDisplay *display) + { + draw_surface (display, subsurface_surface, 500, 300, 0xff007f00); + } +@@ -87,10 +85,11 @@ actor_destroyed (void *data, + struct wl_callback *callback, + uint32_t serial) + { ++ WaylandDisplay *display = data; + g_assert_cmpint (state, ==, STATE_WAIT_FOR_ACTOR_DESTROYED); + + wl_subsurface_destroy (subsurface); +- init_surfaces (); ++ init_surfaces (display); + state = STATE_WAIT_FOR_CONFIGURE_2; + + wl_callback_destroy (callback); +@@ -101,12 +100,12 @@ static const struct wl_callback_listener actor_destroy_listener = { + }; + + static void +-reset_surface (void) ++reset_surface (WaylandDisplay *display) + { + struct wl_callback *callback; + + callback = test_driver_sync_actor_destroyed (display->test_driver, surface); +- wl_callback_add_listener (callback, &actor_destroy_listener, NULL); ++ wl_callback_add_listener (callback, &actor_destroy_listener, display); + + xdg_toplevel_destroy (xdg_toplevel); + xdg_surface_destroy (xdg_surface); +@@ -120,10 +119,12 @@ handle_frame_callback (void *data, + struct wl_callback *callback, + uint32_t time) + { ++ WaylandDisplay *display = data; ++ + switch (state) + { + case STATE_WAIT_FOR_FRAME_1: +- reset_surface (); ++ reset_surface (display); + break; + case STATE_WAIT_FOR_FRAME_2: + exit (EXIT_SUCCESS); +@@ -141,16 +142,18 @@ handle_xdg_surface_configure (void *data, + struct xdg_surface *xdg_surface, + uint32_t serial) + { ++ WaylandDisplay *display = data; ++ + switch (state) + { + case STATE_INIT: + g_assert_not_reached (); + case STATE_WAIT_FOR_CONFIGURE_1: +- draw_main (); ++ draw_main (display); + state = STATE_WAIT_FOR_FRAME_1; + break; + case STATE_WAIT_FOR_CONFIGURE_2: +- draw_main (); ++ draw_main (display); + state = STATE_WAIT_FOR_FRAME_2; + break; + default: +@@ -160,7 +163,7 @@ handle_xdg_surface_configure (void *data, + + xdg_surface_ack_configure (xdg_surface, serial); + frame_callback = wl_surface_frame (surface); +- wl_callback_add_listener (frame_callback, &frame_listener, NULL); ++ wl_callback_add_listener (frame_callback, &frame_listener, display); + wl_surface_commit (surface); + wl_display_flush (display->display); + } +@@ -170,11 +173,11 @@ static const struct xdg_surface_listener xdg_surface_listener = { + }; + + static void +-init_surfaces (void) ++init_surfaces (WaylandDisplay *display) + { + surface = wl_compositor_create_surface (display->compositor); + xdg_surface = xdg_wm_base_get_xdg_surface (display->xdg_wm_base, surface); +- xdg_surface_add_listener (xdg_surface, &xdg_surface_listener, NULL); ++ xdg_surface_add_listener (xdg_surface, &xdg_surface_listener, display); + xdg_toplevel = xdg_surface_get_toplevel (xdg_surface); + xdg_toplevel_add_listener (xdg_toplevel, &xdg_toplevel_listener, NULL); + xdg_toplevel_set_title (xdg_toplevel, "subsurface-reparenting-test"); +@@ -190,13 +193,14 @@ int + main (int argc, + char **argv) + { ++ g_autoptr (WaylandDisplay) display = NULL; + display = wayland_display_new (WAYLAND_DISPLAY_CAPABILITY_TEST_DRIVER); + + subsurface_surface = wl_compositor_create_surface (display->compositor); +- draw_subsurface (); ++ draw_subsurface (display); + wl_surface_commit (subsurface_surface); + +- init_surfaces (); ++ init_surfaces (display); + state = STATE_WAIT_FOR_CONFIGURE_1; + + while (TRUE) +diff --git a/src/tests/wayland-test-clients/xdg-activation.c b/src/tests/wayland-test-clients/xdg-activation.c +index a1e78758f..b97a599c2 100644 +--- a/src/tests/wayland-test-clients/xdg-activation.c ++++ b/src/tests/wayland-test-clients/xdg-activation.c +@@ -24,18 +24,15 @@ + + #include "xdg-activation-v1-client-protocol.h" + +-static WaylandDisplay *display; +-static struct wl_registry *registry; + static struct xdg_activation_v1 *activation; + + static struct wl_surface *surface; +-static struct xdg_surface *xdg_surface; +-static struct xdg_toplevel *xdg_toplevel; + + static gboolean running; + + static void +-init_surface (const char *token) ++init_surface (struct xdg_toplevel *xdg_toplevel, ++ const char *token) + { + xdg_toplevel_set_title (xdg_toplevel, "startup notification client"); + xdg_activation_v1_activate (activation, token, surface); +@@ -43,7 +40,7 @@ init_surface (const char *token) + } + + static void +-draw_main (void) ++draw_main (WaylandDisplay *display) + { + draw_surface (display, surface, 700, 500, 0xff00ff00); + } +@@ -74,7 +71,9 @@ handle_xdg_surface_configure (void *data, + struct xdg_surface *xdg_surface, + uint32_t serial) + { +- draw_main (); ++ WaylandDisplay *display = data; ++ ++ draw_main (display); + wl_surface_commit (surface); + + g_assert_cmpint (wl_display_roundtrip (display->display), !=, -1); +@@ -126,7 +125,7 @@ static const struct xdg_activation_token_v1_listener token_listener = { + }; + + static char * +-get_token (void) ++get_token (WaylandDisplay *display) + { + struct xdg_activation_token_v1 *token; + char *token_string = NULL; +@@ -151,6 +150,11 @@ get_token (void) + static void + test_startup_notifications (void) + { ++ g_autoptr (WaylandDisplay) display = NULL; ++ struct wl_registry *registry; ++ struct xdg_toplevel *xdg_toplevel; ++ struct xdg_surface *xdg_surface; ++ + g_autofree char *token = NULL; + + display = wayland_display_new (WAYLAND_DISPLAY_CAPABILITY_NONE); +@@ -162,15 +166,15 @@ test_startup_notifications (void) + + wl_display_roundtrip (display->display); + +- token = get_token (); ++ token = get_token (display); + + surface = wl_compositor_create_surface (display->compositor); + xdg_surface = xdg_wm_base_get_xdg_surface (display->xdg_wm_base, surface); +- xdg_surface_add_listener (xdg_surface, &xdg_surface_listener, NULL); ++ xdg_surface_add_listener (xdg_surface, &xdg_surface_listener, display); + xdg_toplevel = xdg_surface_get_toplevel (xdg_surface); + xdg_toplevel_add_listener (xdg_toplevel, &xdg_toplevel_listener, NULL); + +- init_surface (token); ++ init_surface (xdg_toplevel, token); + + running = TRUE; + while (running) +@@ -185,7 +189,6 @@ test_startup_notifications (void) + g_clear_pointer (&xdg_surface, xdg_surface_destroy); + g_clear_pointer (&activation, xdg_activation_v1_destroy); + g_clear_pointer (®istry, wl_registry_destroy); +- g_clear_object (&display); + } + + int +diff --git a/src/tests/wayland-test-clients/xdg-apply-limits.c b/src/tests/wayland-test-clients/xdg-apply-limits.c +index c386af283..9c30bfe4a 100644 +--- a/src/tests/wayland-test-clients/xdg-apply-limits.c ++++ b/src/tests/wayland-test-clients/xdg-apply-limits.c +@@ -31,15 +31,9 @@ typedef enum _State + STATE_WAIT_FOR_FRAME_2 + } State; + +-static WaylandDisplay *display; +- + static struct wl_surface *surface; +-static struct xdg_surface *xdg_surface; + static struct xdg_toplevel *xdg_toplevel; + +-static struct wl_surface *subsurface_surface; +-static struct wl_subsurface *subsurface; +- + static struct wl_callback *frame_callback; + + static gboolean running; +@@ -71,7 +65,7 @@ static const struct wl_callback_listener actor_destroy_listener = { + }; + + static void +-reset_surface (void) ++reset_surface (WaylandDisplay *display) + { + struct wl_callback *callback; + +@@ -89,13 +83,14 @@ reset_surface (void) + } + + static void +-draw_main (void) ++draw_main (WaylandDisplay *display) + { + draw_surface (display, surface, 700, 500, 0xff00ff00); + } + + static void +-draw_subsurface (void) ++draw_subsurface (WaylandDisplay *display, ++ struct wl_surface *subsurface_surface) + { + draw_surface (display, subsurface_surface, 500, 300, 0xff007f00); + } +@@ -126,10 +121,12 @@ handle_frame_callback (void *data, + struct wl_callback *callback, + uint32_t time) + { ++ WaylandDisplay *display = data; ++ + switch (state) + { + case STATE_WAIT_FOR_FRAME_1: +- reset_surface (); ++ reset_surface (display); + test_driver_sync_point (display->test_driver, 1, NULL); + break; + case STATE_WAIT_FOR_FRAME_2: +@@ -154,16 +151,18 @@ handle_xdg_surface_configure (void *data, + struct xdg_surface *xdg_surface, + uint32_t serial) + { ++ WaylandDisplay *display = data; ++ + switch (state) + { + case STATE_INIT: + g_assert_not_reached (); + case STATE_WAIT_FOR_CONFIGURE_1: +- draw_main (); ++ draw_main (display); + state = STATE_WAIT_FOR_FRAME_1; + break; + case STATE_WAIT_FOR_CONFIGURE_2: +- draw_main (); ++ draw_main (display); + state = STATE_WAIT_FOR_FRAME_2; + break; + case STATE_WAIT_FOR_ACTOR_DESTROYED: +@@ -176,7 +175,7 @@ handle_xdg_surface_configure (void *data, + + xdg_surface_ack_configure (xdg_surface, serial); + frame_callback = wl_surface_frame (surface); +- wl_callback_add_listener (frame_callback, &frame_listener, NULL); ++ wl_callback_add_listener (frame_callback, &frame_listener, display); + wl_surface_commit (surface); + wl_display_flush (display->display); + } +@@ -189,11 +188,16 @@ int + main (int argc, + char **argv) + { ++ g_autoptr (WaylandDisplay) display = NULL; ++ struct xdg_surface *xdg_surface; ++ struct wl_surface *subsurface_surface; ++ struct wl_subsurface *subsurface; ++ + display = wayland_display_new (WAYLAND_DISPLAY_CAPABILITY_TEST_DRIVER); + + surface = wl_compositor_create_surface (display->compositor); + xdg_surface = xdg_wm_base_get_xdg_surface (display->xdg_wm_base, surface); +- xdg_surface_add_listener (xdg_surface, &xdg_surface_listener, NULL); ++ xdg_surface_add_listener (xdg_surface, &xdg_surface_listener, display); + xdg_toplevel = xdg_surface_get_toplevel (xdg_surface); + xdg_toplevel_add_listener (xdg_toplevel, &xdg_toplevel_listener, NULL); + +@@ -202,7 +206,7 @@ main (int argc, + subsurface_surface, + surface); + wl_subsurface_set_position (subsurface, 100, 100); +- draw_subsurface (); ++ draw_subsurface (display, subsurface_surface); + wl_surface_commit (subsurface_surface); + + init_surface (); +diff --git a/src/tests/wayland-test-clients/xdg-foreign.c b/src/tests/wayland-test-clients/xdg-foreign.c +index f2dbec151..fdc4f9f70 100644 +--- a/src/tests/wayland-test-clients/xdg-foreign.c ++++ b/src/tests/wayland-test-clients/xdg-foreign.c +@@ -24,8 +24,6 @@ + #include "xdg-foreign-unstable-v1-client-protocol.h" + #include "xdg-foreign-unstable-v2-client-protocol.h" + +-static WaylandDisplay *display; +- + static struct zxdg_exporter_v1 *exporter_v1; + static struct zxdg_exporter_v2 *exporter_v2; + static struct zxdg_importer_v1 *importer_v1; +@@ -130,10 +128,11 @@ int + main (int argc, + char **argv) + { +- g_autoptr (WaylandSurface) window1; +- g_autoptr (WaylandSurface) window2; +- g_autoptr (WaylandSurface) window3; +- g_autoptr (WaylandSurface) window4; ++ g_autoptr (WaylandSurface) window1 = NULL; ++ g_autoptr (WaylandSurface) window2 = NULL; ++ g_autoptr (WaylandSurface) window3 = NULL; ++ g_autoptr (WaylandSurface) window4 = NULL; ++ g_autoptr (WaylandDisplay) display = NULL; + g_autofree char *handle1 = NULL; + g_autofree char *handle3 = NULL; + struct wl_registry *registry; +@@ -222,7 +221,5 @@ main (int argc, + return EXIT_FAILURE; + } + +- g_object_unref (display); +- + return EXIT_SUCCESS; + } +diff --git a/src/tests/wayland-test-clients/xdg-toplevel-bounds.c b/src/tests/wayland-test-clients/xdg-toplevel-bounds.c +index 861ae515b..d3c72dad6 100644 +--- a/src/tests/wayland-test-clients/xdg-toplevel-bounds.c ++++ b/src/tests/wayland-test-clients/xdg-toplevel-bounds.c +@@ -29,11 +29,7 @@ typedef enum _State + STATE_WAIT_FOR_FRAME_1, + } State; + +-static WaylandDisplay *display; +- + static struct wl_surface *surface; +-static struct xdg_surface *xdg_surface; +-static struct xdg_toplevel *xdg_toplevel; + + static struct wl_callback *frame_callback; + +@@ -44,15 +40,16 @@ static int32_t pending_bounds_width; + static int32_t pending_bounds_height; + + static void +-init_surface (void) ++init_surface (struct xdg_toplevel *xdg_toplevel) + { + xdg_toplevel_set_title (xdg_toplevel, "toplevel-bounds-test"); + wl_surface_commit (surface); + } + + static void +-draw_main (int width, +- int height) ++draw_main (WaylandDisplay *display, ++ int width, ++ int height) + { + draw_surface (display, surface, width, height, 0xff00ff00); + } +@@ -94,6 +91,8 @@ handle_frame_callback (void *data, + struct wl_callback *callback, + uint32_t time) + { ++ WaylandDisplay *display = data; ++ + switch (state) + { + case STATE_WAIT_FOR_FRAME_1: +@@ -114,6 +113,8 @@ handle_xdg_surface_configure (void *data, + struct xdg_surface *xdg_surface, + uint32_t serial) + { ++ WaylandDisplay *display = data; ++ + switch (state) + { + case STATE_INIT: +@@ -122,7 +123,8 @@ handle_xdg_surface_configure (void *data, + g_assert (pending_bounds_width > 0); + g_assert (pending_bounds_height > 0); + +- draw_main (pending_bounds_width - 10, ++ draw_main (display, ++ pending_bounds_width - 10, + pending_bounds_height - 10); + state = STATE_WAIT_FOR_FRAME_1; + break; +@@ -132,7 +134,7 @@ handle_xdg_surface_configure (void *data, + + xdg_surface_ack_configure (xdg_surface, serial); + frame_callback = wl_surface_frame (surface); +- wl_callback_add_listener (frame_callback, &frame_listener, NULL); ++ wl_callback_add_listener (frame_callback, &frame_listener, display); + wl_surface_commit (surface); + wl_display_flush (display->display); + } +@@ -154,17 +156,21 @@ int + main (int argc, + char **argv) + { ++ g_autoptr (WaylandDisplay) display = NULL; ++ struct xdg_toplevel *xdg_toplevel; ++ struct xdg_surface *xdg_surface; ++ + display = wayland_display_new (WAYLAND_DISPLAY_CAPABILITY_TEST_DRIVER | + WAYLAND_DISPLAY_CAPABILITY_XDG_SHELL_V4); + g_signal_connect (display, "sync-event", G_CALLBACK (on_sync_event), NULL); + + surface = wl_compositor_create_surface (display->compositor); + xdg_surface = xdg_wm_base_get_xdg_surface (display->xdg_wm_base, surface); +- xdg_surface_add_listener (xdg_surface, &xdg_surface_listener, NULL); ++ xdg_surface_add_listener (xdg_surface, &xdg_surface_listener, display); + xdg_toplevel = xdg_surface_get_toplevel (xdg_surface); + xdg_toplevel_add_listener (xdg_toplevel, &xdg_toplevel_listener, NULL); + +- init_surface (); ++ init_surface (xdg_toplevel); + state = STATE_WAIT_FOR_CONFIGURE_1; + + wl_surface_commit (surface); +diff --git a/src/tests/wayland-test-clients/ycbcr.c b/src/tests/wayland-test-clients/ycbcr.c +index 1805abb8d..3ce3ec642 100644 +--- a/src/tests/wayland-test-clients/ycbcr.c ++++ b/src/tests/wayland-test-clients/ycbcr.c +@@ -24,8 +24,6 @@ + + #include "wayland-test-client-utils.h" + +-static WaylandDisplay *display; +- + static struct wl_surface *surface; + static struct xdg_surface *xdg_surface; + static struct xdg_toplevel *xdg_toplevel; +@@ -63,8 +61,9 @@ typedef void (*ShaderFunc) (float x, + float *out_cr); + + static void +-draw (uint32_t drm_format, +- ShaderFunc shader) ++draw (WaylandDisplay *display, ++ uint32_t drm_format, ++ ShaderFunc shader) + { + WaylandBuffer *buffer; + uint8_t *planes[4]; +@@ -180,7 +179,7 @@ static const struct xdg_surface_listener xdg_surface_listener = { + }; + + static void +-wait_for_configure (void) ++wait_for_configure (WaylandDisplay *display) + { + waiting_for_configure = TRUE; + while (waiting_for_configure) +@@ -194,6 +193,7 @@ int + main (int argc, + char **argv) + { ++ g_autoptr (WaylandDisplay) display = NULL; + display = wayland_display_new (WAYLAND_DISPLAY_CAPABILITY_TEST_DRIVER); + + surface = wl_compositor_create_surface (display->compositor); +@@ -205,27 +205,25 @@ main (int argc, + xdg_toplevel_set_fullscreen (xdg_toplevel, NULL); + wl_surface_commit (surface); + +- wait_for_configure (); ++ wait_for_configure (display); + +- draw (DRM_FORMAT_YUYV, shader_luma_gradient); ++ draw (display, DRM_FORMAT_YUYV, shader_luma_gradient); + wl_surface_commit (surface); + wait_for_effects_completed (display, surface); + wait_for_view_verified (display, 0); + +- draw (DRM_FORMAT_YUYV, shader_color_gradient); ++ draw (display, DRM_FORMAT_YUYV, shader_color_gradient); + wl_surface_commit (surface); + wait_for_view_verified (display, 1); + +- draw (DRM_FORMAT_YUV420, shader_luma_gradient); ++ draw (display, DRM_FORMAT_YUV420, shader_luma_gradient); + wl_surface_commit (surface); + wait_for_view_verified (display, 2); + +- draw (DRM_FORMAT_YUV420, shader_color_gradient); ++ draw (display, DRM_FORMAT_YUV420, shader_color_gradient); + wl_surface_commit (surface); + wait_for_view_verified (display, 3); + + g_clear_pointer (&xdg_toplevel, xdg_toplevel_destroy); + g_clear_pointer (&xdg_surface, xdg_surface_destroy); +- +- g_clear_object (&display); + } +diff --git a/src/wayland/meta-drm-timeline.c b/src/wayland/meta-drm-timeline.c +new file mode 100644 +index 000000000..6d86fd19f +--- /dev/null ++++ b/src/wayland/meta-drm-timeline.c +@@ -0,0 +1,269 @@ ++/* ++ * Copyright (C) 2023 NVIDIA Corporation. ++ * ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License as ++ * published by the Free Software Foundation; either version 2 of the ++ * License, or (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, but ++ * WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, see . ++ * ++ * Written by: ++ * Austin Shafer ++ */ ++ ++/** ++ * MetaDrmTimeline ++ * ++ * MetaDrmTimeline is a helper for handling DRM syncobj operations. It ++ * can import DRM syncobjs and export eventfds at a particular point. ++ * ++ * This is heavily inspired by wlroot's wlr_render_timeline, written by ++ * Simon Ser. ++ */ ++ ++#include "config.h" ++ ++#include ++#include ++#include ++#ifdef HAVE_EVENTFD ++#include ++#endif ++ ++#include "meta/util.h" ++#include "wayland/meta-drm-timeline.h" ++ ++enum ++{ ++ PROP_0, ++ ++ PROP_DRM_FD, ++ PROP_SYNCOBJ_FD, ++ ++ N_PROPS ++}; ++ ++typedef struct _MetaDrmTimeline ++{ ++ GObject parent; ++ ++ int drm; ++ int drm_syncobj_fd; ++ uint32_t drm_syncobj; ++} MetaDrmTimeline; ++ ++static GParamSpec *obj_props[N_PROPS]; ++ ++static void initable_iface_init (GInitableIface *initable_iface); ++ ++G_DEFINE_FINAL_TYPE_WITH_CODE (MetaDrmTimeline, meta_drm_timeline, G_TYPE_OBJECT, ++ G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, ++ initable_iface_init)) ++ ++static void ++meta_drm_timeline_get_property (GObject *object, ++ guint prop_id, ++ GValue *value, ++ GParamSpec *pspec) ++{ ++ MetaDrmTimeline *timeline = META_DRM_TIMELINE (object); ++ ++ switch (prop_id) ++ { ++ case PROP_DRM_FD: ++ g_value_set_int (value, timeline->drm); ++ break; ++ case PROP_SYNCOBJ_FD: ++ g_value_set_int (value, timeline->drm_syncobj_fd); ++ break; ++ default: ++ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); ++ break; ++ } ++} ++ ++static void ++meta_drm_timeline_set_property (GObject *object, ++ guint prop_id, ++ const GValue *value, ++ GParamSpec *pspec) ++{ ++ MetaDrmTimeline *timeline = META_DRM_TIMELINE (object); ++ int fd; ++ ++ switch (prop_id) ++ { ++ case PROP_DRM_FD: ++ fd = g_value_get_int (value); ++ timeline->drm = fcntl (fd, F_DUPFD_CLOEXEC, 0); ++ break; ++ case PROP_SYNCOBJ_FD: ++ fd = g_value_get_int (value); ++ timeline->drm_syncobj_fd = fcntl (fd, F_DUPFD_CLOEXEC, 0); ++ break; ++ default: ++ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); ++ break; ++ } ++} ++ ++static gboolean ++meta_drm_timeline_initable_init (GInitable *initable, ++ GCancellable *cancellable, ++ GError **error) ++{ ++ MetaDrmTimeline *timeline = META_DRM_TIMELINE (initable); ++ ++ if (drmSyncobjFDToHandle (timeline->drm, ++ timeline->drm_syncobj_fd, ++ &timeline->drm_syncobj) != 0) ++ { ++ g_set_error (error, ++ G_IO_ERROR, ++ G_IO_ERROR_FAILED, ++ "Failed to import DRM syncobj"); ++ return FALSE; ++ } ++ ++ return TRUE; ++} ++ ++static void ++initable_iface_init (GInitableIface *initable_iface) ++{ ++ initable_iface->init = meta_drm_timeline_initable_init; ++} ++ ++MetaDrmTimeline * ++meta_drm_timeline_import_syncobj (int fd, ++ int drm_syncobj, ++ GError **error) ++{ ++ MetaDrmTimeline *timeline = g_initable_new (META_TYPE_DRM_TIMELINE, ++ NULL, error, ++ "drm-fd", fd, ++ "syncobj-fd", drm_syncobj, ++ NULL); ++ ++ return timeline; ++} ++ ++int ++meta_drm_timeline_get_eventfd (MetaDrmTimeline *timeline, ++ uint64_t sync_point, ++ GError **error) ++{ ++ g_autofd int fd = -1; ++ ++#ifdef HAVE_EVENTFD ++ fd = eventfd (0, EFD_CLOEXEC); ++ if (fd < 0) ++ return -1; ++ ++ if (drmSyncobjEventfd (timeline->drm, timeline->drm_syncobj, ++ sync_point, fd, 0) != 0) ++ { ++ g_set_error (error, ++ G_IO_ERROR, ++ G_IO_ERROR_NOT_SUPPORTED, ++ "DRM_IOCTL_SYNCOBJ_EVENTFD: Failed to export eventfd"); ++ return -1; ++ } ++#endif ++ ++ return g_steal_fd (&fd); ++} ++ ++gboolean ++meta_drm_timeline_set_sync_point (MetaDrmTimeline *timeline, ++ uint64_t sync_point, ++ int sync_fd, ++ GError **error) ++{ ++ uint32_t tmp; ++ ++ /* Import our syncfd at a new release point */ ++ if (drmSyncobjCreate (timeline->drm, 0, &tmp) != 0) ++ { ++ g_set_error (error, ++ G_IO_ERROR, ++ G_IO_ERROR_NOT_SUPPORTED, ++ "Failed to create temporary syncobj"); ++ return FALSE; ++ } ++ ++ if (drmSyncobjImportSyncFile (timeline->drm, tmp, sync_fd) != 0) ++ goto end; ++ ++ if (drmSyncobjTransfer (timeline->drm, timeline->drm_syncobj, ++ sync_point, tmp, 0, 0) != 0) ++ goto end; ++ ++ drmSyncobjDestroy (timeline->drm, tmp); ++ return TRUE; ++ ++end: ++ drmSyncobjDestroy (timeline->drm, tmp); ++ g_set_error (error, ++ G_IO_ERROR, ++ G_IO_ERROR_NOT_SUPPORTED, ++ "Failed to import syncfd at specified point"); ++ return FALSE; ++} ++ ++static void ++meta_drm_timeline_finalize (GObject *object) ++{ ++ MetaDrmTimeline *timeline = META_DRM_TIMELINE (object); ++ ++ drmSyncobjDestroy (timeline->drm, timeline->drm_syncobj); ++ g_clear_fd (&timeline->drm_syncobj_fd, NULL); ++ g_clear_fd (&timeline->drm, NULL); ++ ++ G_OBJECT_CLASS (meta_drm_timeline_parent_class)->finalize (object); ++} ++ ++static void ++meta_drm_timeline_init (MetaDrmTimeline *timeline) ++{ ++ timeline->drm = -1; ++ timeline->drm_syncobj_fd = -1; ++ timeline->drm_syncobj = -1; ++} ++ ++static void ++meta_drm_timeline_class_init (MetaDrmTimelineClass *klass) ++{ ++ GObjectClass *object_class = G_OBJECT_CLASS (klass); ++ ++ object_class->get_property = meta_drm_timeline_get_property; ++ object_class->set_property = meta_drm_timeline_set_property; ++ object_class->finalize = meta_drm_timeline_finalize; ++ ++ obj_props[PROP_DRM_FD] = ++ g_param_spec_int ("drm-fd", ++ NULL, ++ NULL, ++ 0, INT_MAX, 0, ++ G_PARAM_READWRITE | ++ G_PARAM_CONSTRUCT_ONLY | ++ G_PARAM_STATIC_STRINGS); ++ ++ obj_props[PROP_SYNCOBJ_FD] = ++ g_param_spec_int ("syncobj-fd", ++ NULL, ++ NULL, ++ 0, INT_MAX, 0, ++ G_PARAM_READWRITE | ++ G_PARAM_CONSTRUCT_ONLY | ++ G_PARAM_STATIC_STRINGS); ++ ++ g_object_class_install_properties (object_class, N_PROPS, obj_props); ++} +diff --git a/src/wayland/meta-drm-timeline.h b/src/wayland/meta-drm-timeline.h +new file mode 100644 +index 000000000..8206e3066 +--- /dev/null ++++ b/src/wayland/meta-drm-timeline.h +@@ -0,0 +1,47 @@ ++/* ++ * Copyright (C) 2023 NVIDIA Corporation. ++ * ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License as ++ * published by the Free Software Foundation; either version 2 of the ++ * License, or (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, but ++ * WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, see . ++ * ++ * Written by: ++ * Austin Shafer ++ */ ++ ++#pragma once ++ ++#include ++#include ++#include ++ ++#define META_TYPE_DRM_TIMELINE (meta_drm_timeline_get_type ()) ++G_DECLARE_FINAL_TYPE (MetaDrmTimeline, meta_drm_timeline, ++ META, DRM_TIMELINE, GObject); ++ ++typedef struct _MetaDrmTimeline MetaDrmTimeline; ++ ++MetaDrmTimeline * meta_drm_timeline_create (int fd, ++ GError **error); ++ ++MetaDrmTimeline * meta_drm_timeline_import_syncobj (int fd, ++ int drm_syncobj, ++ GError **error); ++ ++int meta_drm_timeline_get_eventfd (MetaDrmTimeline *timeline, ++ uint64_t sync_point, ++ GError **error); ++ ++gboolean meta_drm_timeline_set_sync_point (MetaDrmTimeline *timeline, ++ uint64_t sync_point, ++ int sync_fd, ++ GError **error); +diff --git a/src/wayland/meta-wayland-buffer.c b/src/wayland/meta-wayland-buffer.c +index 59360e30a..3f64758a5 100644 +--- a/src/wayland/meta-wayland-buffer.c ++++ b/src/wayland/meta-wayland-buffer.c +@@ -49,6 +49,7 @@ + #include "wayland/meta-wayland-buffer.h" + + #include ++#include + + #include "backends/meta-backend-private.h" + #include "clutter/clutter.h" +@@ -59,6 +60,8 @@ + #include "common/meta-cogl-drm-formats.h" + #include "common/meta-drm-format-helpers.h" + #include "compositor/meta-multi-texture-format-private.h" ++#include "wayland/meta-drm-timeline.h" ++#include "wayland/meta-wayland-linux-drm-syncobj.h" + + #ifdef HAVE_NATIVE_BACKEND + #include "backends/native/meta-drm-buffer-gbm.h" +@@ -712,12 +715,43 @@ meta_wayland_buffer_inc_use_count (MetaWaylandBuffer *buffer) + void + meta_wayland_buffer_dec_use_count (MetaWaylandBuffer *buffer) + { ++ MetaContext *context = meta_wayland_compositor_get_context (buffer->compositor); ++ MetaBackend *backend = meta_context_get_backend (context); ++ ClutterBackend *clutter_backend = meta_backend_get_clutter_backend (backend); ++ CoglContext *cogl_context = clutter_backend_get_cogl_context (clutter_backend); ++ MetaWaylandSyncPoint *sync_point; ++ g_autoptr(GError) error = NULL; ++ g_autofd int sync_fd = -1; ++ + g_return_if_fail (buffer->use_count > 0); + + buffer->use_count--; + + if (buffer->use_count == 0 && buffer->resource) +- wl_buffer_send_release (buffer->resource); ++ { ++ wl_buffer_send_release (buffer->resource); ++ ++ sync_fd = cogl_context_get_latest_sync_fd (cogl_context); ++ if (sync_fd < 0) ++ { ++ meta_topic (META_DEBUG_WAYLAND, "Invalid Sync Fd returned by COGL"); ++ return; ++ } ++ ++ for (int i = 0; i < buffer->release_points->len; i++) ++ { ++ sync_point = g_ptr_array_index (buffer->release_points, i); ++ if (!meta_wayland_sync_timeline_set_sync_point (sync_point->timeline, ++ sync_point->sync_point, ++ sync_fd, ++ &error)) ++ { ++ g_warning ("Failed to import sync point: %s", error->message); ++ } ++ } ++ g_ptr_array_remove_range (buffer->release_points, 0, ++ buffer->release_points->len); ++ } + } + + gboolean +@@ -981,6 +1015,7 @@ meta_wayland_buffer_finalize (GObject *object) + + clear_tainted_scanout_onscreens (buffer); + g_clear_pointer (&buffer->tainted_scanout_onscreens, g_hash_table_unref); ++ g_clear_pointer (&buffer->release_points, g_ptr_array_unref); + + g_clear_object (&buffer->egl_image.texture); + #ifdef HAVE_WAYLAND_EGLSTREAM +@@ -999,6 +1034,7 @@ meta_wayland_buffer_finalize (GObject *object) + static void + meta_wayland_buffer_init (MetaWaylandBuffer *buffer) + { ++ buffer->release_points = g_ptr_array_new_with_free_func (g_free); + } + + static void +@@ -1091,7 +1127,9 @@ meta_wayland_init_shm (MetaWaylandCompositor *compositor) + + drm_format = shm_to_drm_format (possible_formats[i]); + format_info = meta_format_info_from_drm_format (drm_format); +- g_assert (format_info); ++ ++ if (!format_info) ++ continue; + + if (!context_supports_format (cogl_context, format_info)) + continue; +diff --git a/src/wayland/meta-wayland-buffer.h b/src/wayland/meta-wayland-buffer.h +index 23aea7cb2..a0d59f51b 100644 +--- a/src/wayland/meta-wayland-buffer.h ++++ b/src/wayland/meta-wayland-buffer.h +@@ -79,6 +79,8 @@ struct _MetaWaylandBuffer + } single_pixel; + + GHashTable *tainted_scanout_onscreens; ++ ++ GPtrArray *release_points; + }; + + #define META_TYPE_WAYLAND_BUFFER (meta_wayland_buffer_get_type ()) +diff --git a/src/wayland/meta-wayland-dma-buf.c b/src/wayland/meta-wayland-dma-buf.c +index 8b326ecfa..5e4ee671c 100644 +--- a/src/wayland/meta-wayland-dma-buf.c ++++ b/src/wayland/meta-wayland-dma-buf.c +@@ -57,6 +57,7 @@ + #include "wayland/meta-wayland-buffer.h" + #include "wayland/meta-wayland-private.h" + #include "wayland/meta-wayland-versions.h" ++#include "wayland/meta-wayland-linux-drm-syncobj.h" + + #ifdef HAVE_NATIVE_BACKEND + #include "backends/native/meta-drm-buffer-gbm.h" +@@ -1046,6 +1047,39 @@ meta_wayland_dma_buf_create_source (MetaWaylandBuffer *buffer, + return &source->base; + } + ++GSource * ++meta_wayland_drm_syncobj_create_source (MetaWaylandBuffer *buffer, ++ MetaWaylandSyncobjTimeline *timeline, ++ uint64_t sync_point, ++ MetaWaylandDmaBufSourceDispatch dispatch, ++ gpointer user_data) ++{ ++ MetaWaylandDmaBufSource *source = NULL; ++ g_autofd int sync_fd = -1; ++ g_autoptr(GError) error = NULL; ++ ++ sync_fd = meta_wayland_sync_timeline_get_eventfd (timeline, sync_point, &error); ++ if (sync_fd < 0) ++ { ++ g_warning ("Failed to get sync fd: %s", error->message); ++ return NULL; ++ } ++ ++ if (is_fd_readable (sync_fd)) ++ { ++ return NULL; ++ } ++ ++ source = create_source (buffer, dispatch, user_data); ++ if (!source) ++ return NULL; ++ ++ source->fd_tags[0] = g_source_add_unix_fd (&source->base, sync_fd, G_IO_IN); ++ source->owned_sync_fd[0] = g_steal_fd (&sync_fd); ++ ++ return &source->base; ++} ++ + static void + buffer_params_create_common (struct wl_client *client, + struct wl_resource *params_resource, +diff --git a/src/wayland/meta-wayland-dma-buf.h b/src/wayland/meta-wayland-dma-buf.h +index 8f71dac42..5b2cda734 100644 +--- a/src/wayland/meta-wayland-dma-buf.h ++++ b/src/wayland/meta-wayland-dma-buf.h +@@ -63,6 +63,13 @@ meta_wayland_dma_buf_create_source (MetaWaylandBuffer *buffer, + MetaWaylandDmaBufSourceDispatch dispatch, + gpointer user_data); + ++GSource * ++meta_wayland_drm_syncobj_create_source (MetaWaylandBuffer *buffer, ++ MetaWaylandSyncobjTimeline *timeline, ++ uint64_t sync_point, ++ MetaWaylandDmaBufSourceDispatch dispatch, ++ gpointer user_data); ++ + CoglScanout * + meta_wayland_dma_buf_try_acquire_scanout (MetaWaylandBuffer *buffer, + CoglOnscreen *onscreen, +diff --git a/src/wayland/meta-wayland-linux-drm-syncobj.c b/src/wayland/meta-wayland-linux-drm-syncobj.c +new file mode 100644 +index 000000000..a922415d9 +--- /dev/null ++++ b/src/wayland/meta-wayland-linux-drm-syncobj.c +@@ -0,0 +1,637 @@ ++/* ++ * Copyright (C) 2023 NVIDIA Corporation. ++ * ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License as ++ * published by the Free Software Foundation; either version 2 of the ++ * License, or (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, but ++ * WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, see . ++ * ++ * Written by: ++ * Austin Shafer ++ */ ++ ++#include "config.h" ++ ++#include "backends/native/meta-backend-native-types.h" ++#include "backends/native/meta-device-pool.h" ++#include "backends/native/meta-renderer-native.h" ++#include "meta/util.h" ++#include "wayland/meta-wayland-buffer.h" ++#include "wayland/meta-wayland-linux-drm-syncobj.h" ++#include "wayland/meta-wayland-private.h" ++#include ++#include ++ ++typedef struct _MetaWaylandDrmSyncobjManager ++{ ++ GObject parent; ++ ++ int drm; ++} MetaWaylandDrmSyncobjManager; ++ ++typedef struct _MetaWaylandSyncobjSurface ++{ ++ GObject parent; ++ ++ struct wl_resource *resource; ++ MetaWaylandSurface *surface; ++ gulong surface_destroy_handler_id; ++} MetaWaylandSyncobjSurface; ++ ++typedef struct _MetaWaylandSyncobjTimeline ++{ ++ GObject parent; ++ ++ MetaDrmTimeline *drm_timeline; ++} MetaWaylandSyncobjTimeline; ++ ++#define META_TYPE_WAYLAND_DRM_SYNCOBJ_MANAGER (meta_wayland_drm_syncobj_manager_get_type ()) ++G_DECLARE_FINAL_TYPE (MetaWaylandDrmSyncobjManager, meta_wayland_drm_syncobj_manager, ++ META, WAYLAND_DRM_SYNCOBJ_MANAGER, GObject) ++ ++#define META_TYPE_WAYLAND_SYNCOBJ_SURFACE (meta_wayland_syncobj_surface_get_type ()) ++G_DECLARE_FINAL_TYPE (MetaWaylandSyncobjSurface, meta_wayland_syncobj_surface, ++ META, WAYLAND_SYNCOBJ_SURFACE, GObject) ++ ++#define META_TYPE_WAYLAND_SYNCOBJ_TIMELINE (meta_wayland_syncobj_timeline_get_type ()) ++G_DECLARE_FINAL_TYPE (MetaWaylandSyncobjTimeline, meta_wayland_syncobj_timeline, ++ META, WAYLAND_SYNCOBJ_TIMELINE, GObject) ++ ++#define META_TYPE_WAYLAND_DRM_SYNCOBJ_MANAGER (meta_wayland_drm_syncobj_manager_get_type ()) ++G_DEFINE_FINAL_TYPE (MetaWaylandDrmSyncobjManager, meta_wayland_drm_syncobj_manager, ++ G_TYPE_OBJECT) ++ ++#define META_TYPE_WAYLAND_SYNCOBJ_SURFACE (meta_wayland_syncobj_surface_get_type ()) ++G_DEFINE_FINAL_TYPE (MetaWaylandSyncobjSurface, meta_wayland_syncobj_surface, ++ G_TYPE_OBJECT) ++ ++#define META_TYPE_WAYLAND_SYNCOBJ_TIMELINE (meta_wayland_syncobj_timeline_get_type ()) ++G_DEFINE_FINAL_TYPE (MetaWaylandSyncobjTimeline, meta_wayland_syncobj_timeline, ++ G_TYPE_OBJECT) ++ ++G_DEFINE_FINAL_TYPE (MetaWaylandSyncPoint, meta_wayland_sync_point, G_TYPE_OBJECT); ++ ++static GQuark quark_syncobj_surface; ++ ++static void ++meta_wayland_sync_point_set (MetaWaylandSyncPoint **sync_point_ptr, ++ MetaWaylandSyncobjTimeline *syncobj_timeline, ++ uint32_t point_hi, ++ uint32_t point_lo) ++{ ++ MetaWaylandSyncPoint *sync_point; ++ ++ if (!*sync_point_ptr) ++ *sync_point_ptr = g_object_new (META_TYPE_WAYLAND_SYNC_POINT, NULL); ++ ++ sync_point = *sync_point_ptr; ++ g_set_object (&sync_point->timeline, syncobj_timeline); ++ sync_point->sync_point = (uint64_t)point_hi << 32 | point_lo; ++} ++ ++static void ++meta_wayland_sync_point_finalize (GObject *object) ++{ ++ MetaWaylandSyncPoint *sync = META_WAYLAND_SYNC_POINT (object); ++ ++ g_object_unref (sync->timeline); ++ ++ G_OBJECT_CLASS (meta_wayland_sync_point_parent_class)->finalize (object); ++} ++ ++static void ++meta_wayland_sync_point_init (MetaWaylandSyncPoint *sync) ++{ ++} ++ ++static void ++meta_wayland_sync_point_class_init (MetaWaylandSyncPointClass *klass) ++{ ++ GObjectClass *object_class = G_OBJECT_CLASS (klass); ++ ++ object_class->finalize = meta_wayland_sync_point_finalize; ++} ++ ++static void ++syncobj_timeline_handle_resource_destroy (struct wl_resource *resource) ++{ ++ MetaWaylandSyncobjTimeline *syncobj_timeline = ++ wl_resource_get_user_data (resource); ++ g_object_unref (syncobj_timeline); ++} ++ ++static void ++meta_wayland_syncobj_timeline_finalize (GObject *object) ++{ ++ MetaWaylandSyncobjTimeline *syncobj_timeline = ++ META_WAYLAND_SYNCOBJ_TIMELINE (object); ++ ++ g_clear_object (&syncobj_timeline->drm_timeline); ++ ++ G_OBJECT_CLASS (meta_wayland_syncobj_timeline_parent_class)->finalize (object); ++} ++ ++static void ++meta_wayland_syncobj_timeline_class_init (MetaWaylandSyncobjTimelineClass *klass) ++{ ++ GObjectClass *object_class = G_OBJECT_CLASS (klass); ++ ++ object_class->finalize = meta_wayland_syncobj_timeline_finalize; ++} ++ ++static void ++meta_wayland_syncobj_timeline_init (MetaWaylandSyncobjTimeline *syncobj_timeline) ++{ ++ syncobj_timeline->drm_timeline = NULL; ++} ++ ++static void ++syncobj_timeline_handle_destroy (struct wl_client *client, ++ struct wl_resource *resource) ++{ ++ wl_resource_destroy (resource); ++} ++ ++static const struct wp_linux_drm_syncobj_timeline_v1_interface ++ syncobj_timeline_implementation = ++{ ++ syncobj_timeline_handle_destroy, ++}; ++ ++gboolean ++meta_wayland_sync_timeline_set_sync_point (MetaWaylandSyncobjTimeline *timeline, ++ uint64_t sync_point, ++ int sync_fd, ++ GError **error) ++{ ++ return meta_drm_timeline_set_sync_point (timeline->drm_timeline, ++ sync_point, ++ sync_fd, ++ error); ++} ++ ++int ++meta_wayland_sync_timeline_get_eventfd (MetaWaylandSyncobjTimeline *timeline, ++ uint64_t sync_point, ++ GError **error) ++{ ++ return meta_drm_timeline_get_eventfd (timeline->drm_timeline, ++ sync_point, ++ error); ++} ++ ++static void ++syncobj_surface_handle_destroy (struct wl_client *client, ++ struct wl_resource *resource) ++{ ++ wl_resource_destroy (resource); ++} ++ ++static void ++syncobj_surface_handle_set_acquire_point (struct wl_client *client, ++ struct wl_resource *resource, ++ struct wl_resource *timeline_resource, ++ uint32_t point_hi, ++ uint32_t point_lo) ++{ ++ MetaWaylandSyncobjSurface *syncobj_surface = wl_resource_get_user_data (resource); ++ MetaWaylandSurface *surface = syncobj_surface->surface; ++ MetaWaylandSyncobjTimeline *syncobj_timeline = ++ wl_resource_get_user_data (timeline_resource); ++ ++ if (!surface) ++ { ++ wl_resource_post_error (resource, ++ WP_LINUX_DRM_SYNCOBJ_SURFACE_V1_ERROR_NO_SURFACE, ++ "Underlying surface object has been destroyed"); ++ return; ++ } ++ ++ meta_wayland_sync_point_set (&surface->pending_state->drm_syncobj.acquire, ++ syncobj_timeline, ++ point_hi, ++ point_lo); ++} ++ ++static void syncobj_surface_handle_set_release_point (struct wl_client *client, ++ struct wl_resource *resource, ++ struct wl_resource *timeline_resource, ++ uint32_t point_hi, ++ uint32_t point_lo) ++{ ++ MetaWaylandSyncobjSurface *syncobj_surface = wl_resource_get_user_data (resource); ++ MetaWaylandSurface *surface = syncobj_surface->surface; ++ MetaWaylandSyncobjTimeline *syncobj_timeline = ++ wl_resource_get_user_data (timeline_resource); ++ ++ if (!surface) ++ { ++ wl_resource_post_error (resource, ++ WP_LINUX_DRM_SYNCOBJ_SURFACE_V1_ERROR_NO_SURFACE, ++ "Underlying surface object has been destroyed"); ++ return; ++ } ++ ++ meta_wayland_sync_point_set (&surface->pending_state->drm_syncobj.release, ++ syncobj_timeline, ++ point_hi, ++ point_lo); ++} ++ ++static const struct wp_linux_drm_syncobj_surface_v1_interface ++ syncobj_surface_implementation = ++{ ++ syncobj_surface_handle_destroy, ++ syncobj_surface_handle_set_acquire_point, ++ syncobj_surface_handle_set_release_point, ++}; ++ ++static void ++syncobj_surface_resource_destroyed (MetaWaylandSurface *surface, ++ MetaWaylandSyncobjSurface *syncobj_surface) ++{ ++ g_clear_signal_handler (&syncobj_surface->surface_destroy_handler_id, ++ syncobj_surface->surface); ++ ++ g_object_set_qdata (G_OBJECT (syncobj_surface->surface), ++ quark_syncobj_surface, ++ NULL); ++ ++ syncobj_surface->surface = NULL; ++} ++ ++static void ++syncobj_surface_destructor (struct wl_resource *resource) ++{ ++ MetaWaylandSyncobjSurface *syncobj_surface = ++ wl_resource_get_user_data (resource); ++ ++ if (syncobj_surface->surface) ++ syncobj_surface_resource_destroyed (syncobj_surface->surface, syncobj_surface); ++ ++ g_object_unref (syncobj_surface); ++} ++ ++static void ++meta_wayland_syncobj_surface_class_init (MetaWaylandSyncobjSurfaceClass *klass) ++{ ++} ++ ++static void ++meta_wayland_syncobj_surface_init (MetaWaylandSyncobjSurface *syncobj_surface) ++{ ++} ++ ++static void ++drm_syncobj_manager_handle_destroy (struct wl_client *client, ++ struct wl_resource *resource) ++{ ++ wl_resource_destroy (resource); ++} ++ ++static void ++drm_syncobj_manager_handle_get_surface (struct wl_client *client, ++ struct wl_resource *resource, ++ uint32_t id, ++ struct wl_resource *surface_resource) ++{ ++ MetaWaylandSurface *surface = wl_resource_get_user_data (surface_resource); ++ MetaWaylandSyncobjSurface *syncobj_surface = ++ g_object_get_qdata (G_OBJECT (surface), quark_syncobj_surface); ++ struct wl_resource *sync_resource; ++ ++ if (syncobj_surface) ++ { ++ wl_resource_post_error (surface_resource, ++ WP_LINUX_DRM_SYNCOBJ_MANAGER_V1_ERROR_SURFACE_EXISTS, ++ "DRM Syncobj surface object already created for surface %d", ++ wl_resource_get_id (surface_resource)); ++ return; ++ } ++ ++ sync_resource = ++ wl_resource_create (client, ++ &wp_linux_drm_syncobj_surface_v1_interface, ++ wl_resource_get_version (resource), ++ id); ++ if (sync_resource == NULL) ++ { ++ wl_resource_post_no_memory (resource); ++ return; ++ } ++ ++ syncobj_surface = g_object_new (META_TYPE_WAYLAND_SYNCOBJ_SURFACE, NULL); ++ syncobj_surface->surface = surface; ++ syncobj_surface->surface_destroy_handler_id = ++ g_signal_connect (surface, ++ "destroy", ++ G_CALLBACK (syncobj_surface_resource_destroyed), ++ syncobj_surface); ++ ++ g_object_set_qdata (G_OBJECT (surface), ++ quark_syncobj_surface, ++ syncobj_surface); ++ ++ wl_resource_set_implementation (sync_resource, ++ &syncobj_surface_implementation, ++ syncobj_surface, ++ syncobj_surface_destructor); ++ syncobj_surface->resource = sync_resource; ++} ++ ++static void ++drm_syncobj_manager_handle_import_timeline (struct wl_client *client, ++ struct wl_resource *resource, ++ uint32_t id, ++ int drm_syncobj_fd) ++{ ++ MetaWaylandDrmSyncobjManager *drm_syncobj = wl_resource_get_user_data (resource); ++ g_autoptr (GError) error = NULL; ++ g_autoptr (MetaDrmTimeline) drm_timeline = NULL; ++ g_autoptr (MetaWaylandSyncobjTimeline) syncobj_timeline = NULL; ++ struct wl_resource *timeline_resource; ++ ++ drm_timeline = meta_drm_timeline_import_syncobj (drm_syncobj->drm, ++ drm_syncobj_fd, ++ &error); ++ close (drm_syncobj_fd); ++ if (!drm_timeline) ++ { ++ wl_resource_post_error (resource, ++ WP_LINUX_DRM_SYNCOBJ_MANAGER_V1_ERROR_INVALID_TIMELINE, ++ "Failed to import DRM syncobj: %s", ++ error->message); ++ return; ++ } ++ ++ syncobj_timeline = g_object_new (META_TYPE_WAYLAND_SYNCOBJ_TIMELINE, NULL); ++ ++ timeline_resource = wl_resource_create (client, ++ &wp_linux_drm_syncobj_timeline_v1_interface, ++ wl_resource_get_version (resource), ++ id); ++ if (timeline_resource == NULL) ++ { ++ wl_resource_post_no_memory (resource); ++ return; ++ } ++ ++ syncobj_timeline->drm_timeline = g_steal_pointer (&drm_timeline); ++ wl_resource_set_implementation (timeline_resource, ++ &syncobj_timeline_implementation, ++ g_steal_pointer (&syncobj_timeline), ++ syncobj_timeline_handle_resource_destroy); ++} ++ ++static const struct wp_linux_drm_syncobj_manager_v1_interface ++ drm_syncobj_manager_implementation = ++{ ++ drm_syncobj_manager_handle_destroy, ++ drm_syncobj_manager_handle_get_surface, ++ drm_syncobj_manager_handle_import_timeline, ++}; ++ ++static void ++meta_wayland_drm_syncobj_manager_finalize (GObject *object) ++{ ++ MetaWaylandDrmSyncobjManager *drm_syncobj = ++ META_WAYLAND_DRM_SYNCOBJ_MANAGER (object); ++ ++ g_clear_fd (&drm_syncobj->drm, NULL); ++ ++ G_OBJECT_CLASS (meta_wayland_drm_syncobj_manager_parent_class)->finalize (object); ++} ++ ++static void ++meta_wayland_drm_syncobj_manager_class_init (MetaWaylandDrmSyncobjManagerClass *klass) ++{ ++ GObjectClass *object_class = G_OBJECT_CLASS (klass); ++ ++ object_class->finalize = meta_wayland_drm_syncobj_manager_finalize; ++ ++ quark_syncobj_surface = g_quark_from_static_string ("drm-syncobj-quark"); ++} ++ ++static void ++meta_wayland_drm_syncobj_manager_init (MetaWaylandDrmSyncobjManager *drm_syncobj) ++{ ++ drm_syncobj->drm = -1; ++} ++ ++static void ++drm_syncobj_manager_bind (struct wl_client *client, ++ void *user_data, ++ uint32_t version, ++ uint32_t id) ++{ ++ MetaWaylandDrmSyncobjManager *drm_syncobj_manager = user_data; ++ struct wl_resource *resource; ++ ++ resource = wl_resource_create (client, ++ &wp_linux_drm_syncobj_manager_v1_interface, ++ version, ++ id); ++ wl_resource_set_implementation (resource, ++ &drm_syncobj_manager_implementation, ++ drm_syncobj_manager, ++ NULL); ++} ++ ++static MetaWaylandDrmSyncobjManager * ++meta_wayland_drm_syncobj_manager_new (MetaWaylandCompositor *compositor, ++ GError **error) ++{ ++ MetaContext *context = ++ meta_wayland_compositor_get_context (compositor); ++ MetaBackend *backend = meta_context_get_backend (context); ++ MetaEgl *egl = meta_backend_get_egl (backend); ++ ClutterBackend *clutter_backend = meta_backend_get_clutter_backend (backend); ++ CoglContext *cogl_context = clutter_backend_get_cogl_context (clutter_backend); ++ EGLDisplay egl_display = cogl_egl_context_get_egl_display (cogl_context); ++ MetaWaylandDrmSyncobjManager *drm_syncobj_manager; ++ EGLDeviceEXT egl_device; ++ g_autofd int drm_fd = -1; ++ EGLAttrib attrib; ++ uint64_t timeline_supported = false; ++ const char *device_path = NULL; ++ ++ g_assert (backend && egl && clutter_backend && cogl_context && egl_display); ++ ++ if (!meta_egl_query_display_attrib (egl, egl_display, ++ EGL_DEVICE_EXT, &attrib, ++ error)) ++ return NULL; ++ ++ egl_device = (EGLDeviceEXT) attrib; ++ ++ if (meta_egl_egl_device_has_extensions (egl, egl_device, NULL, ++ "EGL_EXT_device_drm_render_node", ++ NULL)) ++ { ++ if (!meta_egl_query_device_string (egl, egl_device, ++ EGL_DRM_RENDER_NODE_FILE_EXT, ++ &device_path, error)) ++ return NULL; ++ } ++ ++ if (!device_path && ++ meta_egl_egl_device_has_extensions (egl, egl_device, NULL, ++ "EGL_EXT_device_drm", ++ NULL)) ++ { ++ if (!meta_egl_query_device_string (egl, egl_device, ++ EGL_DRM_DEVICE_FILE_EXT, ++ &device_path, error)) ++ return NULL; ++ } ++ ++ if (!device_path) ++ { ++ g_set_error (error, ++ G_IO_ERROR, ++ G_IO_ERROR_NOT_SUPPORTED, ++ "Failed to find EGL device to initialize linux-drm-syncobj-v1"); ++ return NULL; ++ } ++ ++ drm_fd = open (device_path, O_RDWR | O_CLOEXEC); ++ if (drm_fd < 0) ++ { ++ g_set_error (error, ++ G_IO_ERROR, ++ G_IO_ERROR_FAILED, ++ "Failed to open DRM device %s", ++ device_path); ++ return NULL; ++ } ++ ++ if (drmGetCap (drm_fd, DRM_CAP_SYNCOBJ_TIMELINE, &timeline_supported) != 0 ++ || !timeline_supported) ++ { ++ g_set_error (error, ++ G_IO_ERROR, ++ G_IO_ERROR_NOT_SUPPORTED, ++ "Failed to check DRM syncobj timeline capability"); ++ return NULL; ++ } ++ ++#ifdef HAVE_EVENTFD ++ if (drmSyncobjEventfd (drm_fd, 0, 0, -1, 0) != -1 || errno != ENOENT) ++#endif ++ { ++ g_set_error (error, ++ G_IO_ERROR, ++ G_IO_ERROR_NOT_SUPPORTED, ++ "drmSyncobjEventfd failed: linux-drm-syncobj requires eventfd support"); ++ return NULL; ++ } ++ ++ drm_syncobj_manager = g_object_new (META_TYPE_WAYLAND_DRM_SYNCOBJ_MANAGER, NULL); ++ drm_syncobj_manager->drm = g_steal_fd (&drm_fd); ++ ++ if (!wl_global_create (compositor->wayland_display, ++ &wp_linux_drm_syncobj_manager_v1_interface, ++ 1, ++ drm_syncobj_manager, ++ drm_syncobj_manager_bind)) ++ { ++ g_error ("Failed to create wp_linux_drm_syncobj_manager_v1_interface global"); ++ } ++ ++ return drm_syncobj_manager; ++} ++ ++void ++meta_wayland_drm_syncobj_init (MetaWaylandCompositor *compositor) ++{ ++ g_autoptr (GError) error = NULL; ++ MetaWaylandDrmSyncobjManager *manager = ++ meta_wayland_drm_syncobj_manager_new (compositor, &error); ++ ++ if (!manager) ++ { ++ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED)) ++ { ++ meta_topic (META_DEBUG_WAYLAND, "Disabling explicit sync: %s", ++ error->message); ++ } ++ else ++ { ++ g_warning ("Failed to create linux-drm-syncobj-manager: %s", ++ error->message); ++ } ++ return; ++ } ++ ++ g_object_set_data_full (G_OBJECT (compositor), "-meta-wayland-drm-syncobj-manager", ++ manager, ++ g_object_unref); ++} ++ ++/* ++ * Validate that the appropriate acquire and release points have been set ++ * for this surface. ++ */ ++bool ++meta_wayland_surface_explicit_sync_validate (MetaWaylandSurface *surface, ++ MetaWaylandSurfaceState *state) ++{ ++ MetaWaylandSyncobjSurface *syncobj_surface = g_object_get_qdata (G_OBJECT (surface), ++ quark_syncobj_surface); ++ ++ if (!syncobj_surface) ++ return TRUE; ++ ++ if (state->buffer) ++ { ++ if (state->buffer->type != META_WAYLAND_BUFFER_TYPE_DMA_BUF) ++ { ++ wl_resource_post_error (syncobj_surface->resource, ++ WP_LINUX_DRM_SYNCOBJ_SURFACE_V1_ERROR_UNSUPPORTED_BUFFER, ++ "Explicit Sync only supported on dmabuf buffers"); ++ return FALSE; ++ } ++ ++ if (!state->drm_syncobj.acquire) ++ { ++ wl_resource_post_error (syncobj_surface->resource, ++ WP_LINUX_DRM_SYNCOBJ_SURFACE_V1_ERROR_NO_ACQUIRE_POINT, ++ "No Acquire point provided"); ++ return FALSE; ++ } ++ ++ if (!state->drm_syncobj.release) ++ { ++ wl_resource_post_error (syncobj_surface->resource, ++ WP_LINUX_DRM_SYNCOBJ_SURFACE_V1_ERROR_NO_RELEASE_POINT, ++ "No Release point provided"); ++ return FALSE; ++ } ++ ++ if (state->drm_syncobj.acquire->timeline == state->drm_syncobj.release->timeline && ++ state->drm_syncobj.acquire->sync_point >= state->drm_syncobj.release->sync_point) ++ { ++ wl_resource_post_error (syncobj_surface->resource, ++ WP_LINUX_DRM_SYNCOBJ_SURFACE_V1_ERROR_CONFLICTING_POINTS, ++ "Invalid Release and Acquire point combination"); ++ return FALSE; ++ } ++ } ++ else if (state->drm_syncobj.acquire || state->drm_syncobj.release) ++ { ++ wl_resource_post_error (syncobj_surface->resource, ++ WP_LINUX_DRM_SYNCOBJ_SURFACE_V1_ERROR_NO_BUFFER, ++ "Release or Acquire point set but no buffer attached"); ++ return FALSE; ++ } ++ ++ return TRUE; ++} +diff --git a/src/wayland/meta-wayland-linux-drm-syncobj.h b/src/wayland/meta-wayland-linux-drm-syncobj.h +new file mode 100644 +index 000000000..cfe361b7c +--- /dev/null ++++ b/src/wayland/meta-wayland-linux-drm-syncobj.h +@@ -0,0 +1,59 @@ ++/* ++ * Copyright (C) 2023 NVIDIA Corporation. ++ * ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License as ++ * published by the Free Software Foundation; either version 2 of the ++ * License, or (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, but ++ * WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, see . ++ * ++ * Written by: ++ * Austin Shafer ++ */ ++ ++#pragma once ++ ++#include ++ ++#include "wayland/meta-wayland-types.h" ++#include "wayland/meta-drm-timeline.h" ++ ++#include "linux-drm-syncobj-v1-server-protocol.h" ++ ++#define META_TYPE_WAYLAND_SYNC_POINT (meta_wayland_sync_point_get_type ()) ++G_DECLARE_FINAL_TYPE (MetaWaylandSyncPoint, ++ meta_wayland_sync_point, ++ META, WAYLAND_SYNC_POINT, ++ GObject) ++ ++typedef struct _MetaWaylandSyncPoint { ++ GObject parent; ++ ++ MetaWaylandSyncobjTimeline *timeline; ++ uint64_t sync_point; ++} MetaWaylandSyncPoint; ++ ++bool ++meta_wayland_surface_explicit_sync_validate (MetaWaylandSurface *surface, ++ MetaWaylandSurfaceState *state); ++ ++void ++meta_wayland_drm_syncobj_init (MetaWaylandCompositor *compositor); ++ ++gboolean ++meta_wayland_sync_timeline_set_sync_point (MetaWaylandSyncobjTimeline *timeline, ++ uint64_t sync_point, ++ int sync_fd, ++ GError **error); ++ ++int ++meta_wayland_sync_timeline_get_eventfd (MetaWaylandSyncobjTimeline *timeline, ++ uint64_t sync_point, ++ GError **error); +diff --git a/src/wayland/meta-wayland-seat.c b/src/wayland/meta-wayland-seat.c +index ebbe533c3..ea4d4608c 100644 +--- a/src/wayland/meta-wayland-seat.c ++++ b/src/wayland/meta-wayland-seat.c +@@ -531,6 +531,8 @@ void + meta_wayland_seat_set_input_focus (MetaWaylandSeat *seat, + MetaWaylandSurface *surface) + { ++ ClutterSeat *clutter_seat; ++ + if (seat->input_focus == surface) + return; + +@@ -551,16 +553,10 @@ meta_wayland_seat_set_input_focus (MetaWaylandSeat *seat, + seat); + } + +- if (meta_wayland_seat_has_keyboard (seat)) +- { +- meta_wayland_keyboard_set_focus (seat->keyboard, surface); +- meta_wayland_data_device_set_keyboard_focus (&seat->data_device); +- meta_wayland_data_device_primary_set_keyboard_focus (&seat->primary_data_device); +- } +- +- meta_wayland_tablet_seat_set_pad_focus (seat->tablet_seat, surface); +- +- meta_wayland_text_input_set_focus (seat->text_input, surface); ++ clutter_seat = clutter_backend_get_default_seat (clutter_get_default_backend ()); ++ meta_wayland_input_invalidate_focus (seat->input_handler, ++ clutter_seat_get_keyboard (clutter_seat), ++ NULL); + } + + MetaWaylandSurface * +diff --git a/src/wayland/meta-wayland-single-pixel-buffer.c b/src/wayland/meta-wayland-single-pixel-buffer.c +index 5b23b917a..be93de802 100644 +--- a/src/wayland/meta-wayland-single-pixel-buffer.c ++++ b/src/wayland/meta-wayland-single-pixel-buffer.c +@@ -122,7 +122,10 @@ meta_wayland_single_pixel_buffer_attach (MetaWaylandBuffer *buffer, + CoglTexture *tex_2d; + + if (buffer->single_pixel.texture) +- return TRUE; ++ { ++ *texture = g_object_ref (buffer->single_pixel.texture); ++ return TRUE; ++ } + + data[0] = single_pixel_buffer->b / (UINT32_MAX / 0xff); + data[1] = single_pixel_buffer->g / (UINT32_MAX / 0xff); +diff --git a/src/wayland/meta-wayland-surface-private.h b/src/wayland/meta-wayland-surface-private.h +index 2b61a2fc8..e3a88c0a3 100644 +--- a/src/wayland/meta-wayland-surface-private.h ++++ b/src/wayland/meta-wayland-surface-private.h +@@ -29,6 +29,7 @@ + #include "meta/meta-wayland-surface.h" + #include "wayland/meta-wayland-pointer-constraints.h" + #include "wayland/meta-wayland-types.h" ++#include "wayland/meta-wayland-linux-drm-syncobj.h" + + #define META_TYPE_WAYLAND_SURFACE_ROLE (meta_wayland_surface_role_get_type ()) + G_DECLARE_DERIVABLE_TYPE (MetaWaylandSurfaceRole, meta_wayland_surface_role, +@@ -128,6 +129,12 @@ struct _MetaWaylandSurfaceState + /* xdg_popup */ + MetaWaylandXdgPositioner *xdg_positioner; + uint32_t xdg_popup_reposition_token; ++ ++ /* Explicit Synchronization */ ++ struct { ++ MetaWaylandSyncPoint *acquire; ++ MetaWaylandSyncPoint *release; ++ } drm_syncobj; + }; + + struct _MetaWaylandDragDestFuncs +diff --git a/src/wayland/meta-wayland-surface.c b/src/wayland/meta-wayland-surface.c +index 0012798df..81ee47bbd 100644 +--- a/src/wayland/meta-wayland-surface.c ++++ b/src/wayland/meta-wayland-surface.c +@@ -48,6 +48,7 @@ + #include "wayland/meta-wayland-viewporter.h" + #include "wayland/meta-wayland-xdg-shell.h" + #include "wayland/meta-window-wayland.h" ++#include "wayland/meta-wayland-linux-drm-syncobj.h" + + #ifdef HAVE_XWAYLAND + #include "wayland/meta-xwayland-private.h" +@@ -446,6 +447,9 @@ meta_wayland_surface_state_set_default (MetaWaylandSurfaceState *state) + wl_list_init (&state->presentation_feedback_list); + + state->xdg_popup_reposition_token = 0; ++ ++ state->drm_syncobj.acquire = NULL; ++ state->drm_syncobj.release = NULL; + } + + static void +@@ -466,6 +470,8 @@ meta_wayland_surface_state_clear (MetaWaylandSurfaceState *state) + MetaWaylandFrameCallback *cb, *next; + + g_clear_object (&state->texture); ++ g_clear_object (&state->drm_syncobj.acquire); ++ g_clear_object (&state->drm_syncobj.release); + + g_clear_pointer (&state->surface_damage, mtk_region_unref); + g_clear_pointer (&state->buffer_damage, mtk_region_unref); +@@ -630,6 +636,11 @@ meta_wayland_surface_state_merge_into (MetaWaylandSurfaceState *from, + to->xdg_positioner = g_steal_pointer (&from->xdg_positioner); + to->xdg_popup_reposition_token = from->xdg_popup_reposition_token; + } ++ ++ g_set_object (&to->drm_syncobj.acquire, from->drm_syncobj.acquire); ++ g_clear_object (&from->drm_syncobj.acquire); ++ g_set_object (&to->drm_syncobj.release, from->drm_syncobj.release); ++ g_clear_object (&from->drm_syncobj.release); + } + + static void +@@ -925,6 +936,7 @@ meta_wayland_surface_commit (MetaWaylandSurface *surface) + MetaWaylandBuffer *buffer = pending->buffer; + MetaWaylandTransaction *transaction; + MetaWaylandSurface *subsurface_surface; ++ MetaWaylandSyncPoint *release_point = pending->drm_syncobj.release; + + COGL_TRACE_BEGIN_SCOPED (MetaWaylandSurfaceCommit, + "Meta::WaylandSurface::commit()"); +@@ -932,6 +944,9 @@ meta_wayland_surface_commit (MetaWaylandSurface *surface) + if (pending->scale > 0) + surface->committed_state.scale = pending->scale; + ++ if (!meta_wayland_surface_explicit_sync_validate (surface, pending)) ++ return; ++ + if (buffer) + { + g_autoptr (GError) error = NULL; +@@ -957,6 +972,9 @@ meta_wayland_surface_commit (MetaWaylandSurface *surface) + + pending->texture = g_object_ref (surface->committed_state.texture); + ++ if (release_point) ++ g_ptr_array_add (buffer->release_points, g_object_ref (release_point)); ++ + g_object_ref (buffer); + meta_wayland_buffer_inc_use_count (buffer); + } +diff --git a/src/wayland/meta-wayland-transaction.c b/src/wayland/meta-wayland-transaction.c +index 70f2b4047..694ce6dbe 100644 +--- a/src/wayland/meta-wayland-transaction.c ++++ b/src/wayland/meta-wayland-transaction.c +@@ -26,6 +26,7 @@ + #include "wayland/meta-wayland.h" + #include "wayland/meta-wayland-buffer.h" + #include "wayland/meta-wayland-dma-buf.h" ++#include "wayland/meta-wayland-linux-drm-syncobj.h" + + #define META_WAYLAND_TRANSACTION_NONE ((void *)(uintptr_t) G_MAXSIZE) + +@@ -314,6 +315,17 @@ meta_wayland_transaction_dma_buf_dispatch (MetaWaylandBuffer *buffer, + meta_wayland_transaction_maybe_apply (transaction); + } + ++static void ++ensure_buf_sources (MetaWaylandTransaction *transaction) ++{ ++ if (!transaction->buf_sources) ++ { ++ transaction->buf_sources = ++ g_hash_table_new_full (NULL, NULL, NULL, ++ (GDestroyNotify) g_source_destroy); ++ } ++} ++ + static gboolean + meta_wayland_transaction_add_dma_buf_source (MetaWaylandTransaction *transaction, + MetaWaylandBuffer *buffer) +@@ -330,12 +342,35 @@ meta_wayland_transaction_add_dma_buf_source (MetaWaylandTransaction *transaction + if (!source) + return FALSE; + +- if (!transaction->buf_sources) +- { +- transaction->buf_sources = +- g_hash_table_new_full (NULL, NULL, NULL, +- (GDestroyNotify) g_source_destroy); +- } ++ ensure_buf_sources (transaction); ++ ++ g_hash_table_insert (transaction->buf_sources, buffer, source); ++ g_source_attach (source, NULL); ++ g_source_unref (source); ++ ++ return TRUE; ++} ++ ++static gboolean ++meta_wayland_transaction_add_drm_syncobj_source (MetaWaylandTransaction *transaction, ++ MetaWaylandBuffer *buffer, ++ MetaWaylandSyncPoint *acquire) ++{ ++ GSource *source; ++ ++ if (transaction->buf_sources && ++ g_hash_table_contains (transaction->buf_sources, buffer)) ++ return FALSE; ++ ++ source = meta_wayland_drm_syncobj_create_source (buffer, ++ acquire->timeline, ++ acquire->sync_point, ++ meta_wayland_transaction_dma_buf_dispatch, ++ transaction); ++ if (!source) ++ return FALSE; ++ ++ ensure_buf_sources (transaction); + + g_hash_table_insert (transaction->buf_sources, buffer, source); + g_source_attach (source, NULL); +@@ -382,8 +417,11 @@ meta_wayland_transaction_commit (MetaWaylandTransaction *transaction) + { + MetaWaylandBuffer *buffer = entry->state->buffer; + +- if (buffer && +- meta_wayland_transaction_add_dma_buf_source (transaction, buffer)) ++ if ((entry->state->drm_syncobj.acquire && ++ meta_wayland_transaction_add_drm_syncobj_source (transaction, buffer, ++ entry->state->drm_syncobj.acquire)) ++ || (buffer && ++ meta_wayland_transaction_add_dma_buf_source (transaction, buffer))) + maybe_apply = FALSE; + + if (entry->state->subsurface_placement_ops) +diff --git a/src/wayland/meta-wayland-types.h b/src/wayland/meta-wayland-types.h +index 40c3bb733..4f224a900 100644 +--- a/src/wayland/meta-wayland-types.h ++++ b/src/wayland/meta-wayland-types.h +@@ -60,6 +60,8 @@ typedef struct _MetaWaylandActivation MetaWaylandActivation; + + typedef struct _MetaWaylandDmaBufManager MetaWaylandDmaBufManager; + ++typedef struct _MetaWaylandSyncobjTimeline MetaWaylandSyncobjTimeline; ++ + typedef struct _MetaWaylandXdgPositioner MetaWaylandXdgPositioner; + + typedef struct _MetaXWaylandManager MetaXWaylandManager; +diff --git a/src/wayland/meta-wayland-window-configuration.c b/src/wayland/meta-wayland-window-configuration.c +index 4298d1eec..56fa77ad6 100644 +--- a/src/wayland/meta-wayland-window-configuration.c ++++ b/src/wayland/meta-wayland-window-configuration.c +@@ -100,14 +100,15 @@ meta_wayland_window_configuration_new_relative (MetaWindow *window, + + MetaWaylandWindowConfiguration * + meta_wayland_window_configuration_new_empty (int bounds_width, +- int bounds_height) ++ int bounds_height, ++ int scale) + { + MetaWaylandWindowConfiguration *configuration; + + configuration = g_new0 (MetaWaylandWindowConfiguration, 1); + *configuration = (MetaWaylandWindowConfiguration) { + .serial = ++global_serial_counter, +- .scale = 1, ++ .scale = scale, + .bounds_width = bounds_width, + .bounds_height = bounds_height, + }; +diff --git a/src/wayland/meta-wayland-window-configuration.h b/src/wayland/meta-wayland-window-configuration.h +index 064e73ea1..fb26344bb 100644 +--- a/src/wayland/meta-wayland-window-configuration.h ++++ b/src/wayland/meta-wayland-window-configuration.h +@@ -68,6 +68,7 @@ MetaWaylandWindowConfiguration * meta_wayland_window_configuration_new_relative + int scale); + + MetaWaylandWindowConfiguration * meta_wayland_window_configuration_new_empty (int bounds_width, +- int bounds_height); ++ int bounds_height, ++ int scale); + + void meta_wayland_window_configuration_free (MetaWaylandWindowConfiguration *configuration); +diff --git a/src/wayland/meta-wayland-xdg-shell.c b/src/wayland/meta-wayland-xdg-shell.c +index 28b39577b..ff1b7a11a 100644 +--- a/src/wayland/meta-wayland-xdg-shell.c ++++ b/src/wayland/meta-wayland-xdg-shell.c +@@ -869,6 +869,9 @@ meta_wayland_xdg_toplevel_apply_state (MetaWaylandSurfaceRole *surface_role, + MetaWaylandWindowConfiguration *configuration; + int bounds_width; + int bounds_height; ++ int geometry_scale; ++ ++ geometry_scale = meta_window_wayland_get_geometry_scale (window); + + if (!meta_window_calculate_bounds (window, &bounds_width, &bounds_height)) + { +@@ -878,7 +881,8 @@ meta_wayland_xdg_toplevel_apply_state (MetaWaylandSurfaceRole *surface_role, + + configuration = + meta_wayland_window_configuration_new_empty (bounds_width, +- bounds_height); ++ bounds_height, ++ geometry_scale); + meta_wayland_xdg_toplevel_send_configure (xdg_toplevel, configuration); + meta_wayland_window_configuration_free (configuration); + return; +diff --git a/src/wayland/meta-wayland.c b/src/wayland/meta-wayland.c +index 7a24cf6b4..501b69a91 100644 +--- a/src/wayland/meta-wayland.c ++++ b/src/wayland/meta-wayland.c +@@ -57,6 +57,7 @@ + #include "wayland/meta-wayland-tablet-manager.h" + #include "wayland/meta-wayland-transaction.h" + #include "wayland/meta-wayland-xdg-foreign.h" ++#include "wayland/meta-wayland-linux-drm-syncobj.h" + + #ifdef HAVE_XWAYLAND + #include "wayland/meta-wayland-x11-interop.h" +@@ -868,6 +869,7 @@ meta_wayland_compositor_new (MetaContext *context) + meta_wayland_activation_init (compositor); + meta_wayland_transaction_init (compositor); + meta_wayland_idle_inhibit_init (compositor); ++ meta_wayland_drm_syncobj_init (compositor); + + #ifdef HAVE_WAYLAND_EGLSTREAM + { +diff --git a/src/wayland/protocol/linux-drm-syncobj-v1.xml b/src/wayland/protocol/linux-drm-syncobj-v1.xml +new file mode 100644 +index 000000000..2c491eaf4 +--- /dev/null ++++ b/src/wayland/protocol/linux-drm-syncobj-v1.xml +@@ -0,0 +1,261 @@ ++ ++ ++ ++ Copyright 2016 The Chromium Authors. ++ Copyright 2017 Intel Corporation ++ Copyright 2018 Collabora, Ltd ++ Copyright 2021 Simon Ser ++ ++ Permission is hereby granted, free of charge, to any person obtaining a ++ copy of this software and associated documentation files (the "Software"), ++ to deal in the Software without restriction, including without limitation ++ the rights to use, copy, modify, merge, publish, distribute, sublicense, ++ and/or sell copies of the Software, and to permit persons to whom the ++ Software is furnished to do so, subject to the following conditions: ++ ++ The above copyright notice and this permission notice (including the next ++ paragraph) shall be included in all copies or substantial portions of the ++ Software. ++ ++ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ++ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, ++ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL ++ THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER ++ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING ++ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER ++ DEALINGS IN THE SOFTWARE. ++ ++ ++ ++ This protocol allows clients to request explicit synchronization for ++ buffers. It is tied to the Linux DRM synchronization object framework. ++ ++ Synchronization refers to co-ordination of pipelined operations performed ++ on buffers. Most GPU clients will schedule an asynchronous operation to ++ render to the buffer, then immediately send the buffer to the compositor ++ to be attached to a surface. ++ ++ With implicit synchronization, ensuring that the rendering operation is ++ complete before the compositor displays the buffer is an implementation ++ detail handled by either the kernel or userspace graphics driver. ++ ++ By contrast, with explicit synchronization, DRM synchronization object ++ timeline points mark when the asynchronous operations are complete. When ++ submitting a buffer, the client provides a timeline point which will be ++ waited on before the compositor accesses the buffer, and another timeline ++ point that the compositor will signal when it no longer needs to access the ++ buffer contents for the purposes of the surface commit. ++ ++ Linux DRM synchronization objects are documented at: ++ https://dri.freedesktop.org/docs/drm/gpu/drm-mm.html#drm-sync-objects ++ ++ Warning! The protocol described in this file is currently in the testing ++ phase. Backward compatible changes may be added together with the ++ corresponding interface version bump. Backward incompatible changes can ++ only be done by creating a new major version of the extension. ++ ++ ++ ++ ++ This global is a factory interface, allowing clients to request ++ explicit synchronization for buffers on a per-surface basis. ++ ++ See wp_linux_drm_syncobj_surface_v1 for more information. ++ ++ ++ ++ ++ Destroy this explicit synchronization factory object. Other objects ++ shall not be affected by this request. ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ Instantiate an interface extension for the given wl_surface to provide ++ explicit synchronization. ++ ++ If the given wl_surface already has an explicit synchronization object ++ associated, the surface_exists protocol error is raised. ++ ++ Graphics APIs, like EGL or Vulkan, that manage the buffer queue and ++ commits of a wl_surface themselves, are likely to be using this ++ extension internally. If a client is using such an API for a ++ wl_surface, it should not directly use this extension on that surface, ++ to avoid raising a surface_exists protocol error. ++ ++ ++ ++ ++ ++ ++ ++ Import a DRM synchronization object timeline. ++ ++ If the FD cannot be imported, the invalid_timeline error is raised. ++ ++ ++ ++ ++ ++ ++ ++ ++ This object represents an explicit synchronization object timeline ++ imported by the client to the compositor. ++ ++ ++ ++ ++ Destroy the synchronization object timeline. Other objects are not ++ affected by this request, in particular timeline points set by ++ set_acquire_point and set_release_point are not unset. ++ ++ ++ ++ ++ ++ ++ This object is an add-on interface for wl_surface to enable explicit ++ synchronization. ++ ++ Each surface can be associated with only one object of this interface at ++ any time. ++ ++ Explicit synchronization is guaranteed to be supported for buffers ++ created with any version of the linux-dmabuf protocol. Compositors are ++ free to support explicit synchronization for additional buffer types. ++ If at surface commit time the attached buffer does not support explicit ++ synchronization, an unsupported_buffer error is raised. ++ ++ As long as the wp_linux_drm_syncobj_surface_v1 object is alive, the ++ compositor may ignore implicit synchronization for buffers attached and ++ committed to the wl_surface. The delivery of wl_buffer.release events ++ for buffers attached to the surface becomes undefined. ++ ++ Clients must set both acquire and release points if and only if a ++ non-null buffer is attached in the same surface commit. See the ++ no_buffer, no_acquire_point and no_release_point protocol errors. ++ ++ If at surface commit time the acquire and release DRM syncobj timelines ++ are identical, the acquire point value must be strictly less than the ++ release point value, or else the conflicting_points protocol error is ++ raised. ++ ++ ++ ++ ++ Destroy this surface synchronization object. ++ ++ Any timeline point set by this object with set_acquire_point or ++ set_release_point since the last commit may be discarded by the ++ compositor. Any timeline point set by this object before the last ++ commit will not be affected. ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ Set the timeline point that must be signalled before the compositor may ++ sample from the buffer attached with wl_surface.attach. ++ ++ The 64-bit unsigned value combined from point_hi and point_lo is the ++ point value. ++ ++ The acquire point is double-buffered state, and will be applied on the ++ next wl_surface.commit request for the associated surface. Thus, it ++ applies only to the buffer that is attached to the surface at commit ++ time. ++ ++ If an acquire point has already been attached during the same commit ++ cycle, the new point replaces the old one. ++ ++ If the associated wl_surface was destroyed, a no_surface error is ++ raised. ++ ++ If at surface commit time there is a pending acquire timeline point set ++ but no pending buffer attached, a no_buffer error is raised. If at ++ surface commit time there is a pending buffer attached but no pending ++ acquire timeline point set, the no_acquire_point protocol error is ++ raised. ++ ++ ++ ++ ++ ++ ++ ++ ++ Set the timeline point that must be signalled by the compositor when it ++ has finished its usage of the buffer attached with wl_surface.attach ++ for the relevant commit. ++ ++ Once the timeline point is signaled, and assuming the associated buffer ++ is not pending release from other wl_surface.commit requests, no ++ additional explicit or implicit synchronization with the compositor is ++ required to safely re-use the buffer. ++ ++ Note that clients cannot rely on the release point being always ++ signaled after the acquire point: compositors may release buffers ++ without ever reading from them. In addition, the compositor may use ++ different presentation paths for different commits, which may have ++ different release behavior. As a result, the compositor may signal the ++ release points in a different order than the client committed them. ++ ++ Because signaling a timeline point also signals every previous point, ++ it is generally not safe to use the same timeline object for the ++ release points of multiple buffers. The out-of-order signaling ++ described above may lead to a release point being signaled before the ++ compositor has finished reading. To avoid this, it is strongly ++ recommended that each buffer should use a separate timeline for its ++ release points. ++ ++ The 64-bit unsigned value combined from point_hi and point_lo is the ++ point value. ++ ++ The release point is double-buffered state, and will be applied on the ++ next wl_surface.commit request for the associated surface. Thus, it ++ applies only to the buffer that is attached to the surface at commit ++ time. ++ ++ If a release point has already been attached during the same commit ++ cycle, the new point replaces the old one. ++ ++ If the associated wl_surface was destroyed, a no_surface error is ++ raised. ++ ++ If at surface commit time there is a pending release timeline point set ++ but no pending buffer attached, a no_buffer error is raised. If at ++ surface commit time there is a pending buffer attached but no pending ++ release timeline point set, the no_release_point protocol error is ++ raised. ++ ++ ++ ++ ++ ++ ++ diff --git a/staging/mutter/mutter.spec b/staging/mutter/mutter.spec index 8aa8fc3..ca43edb 100644 --- a/staging/mutter/mutter.spec +++ b/staging/mutter/mutter.spec @@ -16,7 +16,7 @@ %global _default_patch_fuzz 2 Name: mutter -Version: %{gnome_version}.xscaling.1 +Version: %{gnome_version}.xscaling.2 Release: %autorelease Summary: Window and compositing manager based on Clutter @@ -47,7 +47,15 @@ Patch: 0001-modified-3329.patch # https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/3567 # Scaling for Xwayland applications -Patch: 3567.patch +Patch: 0002-3567.patch + +# https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/3304/ +# Nvidia secondary GPU copy acceleration +Patch: 0003-3304.patch + +# https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1441/ +# Dynamic triple buffering +Patch: 0004-1441.patch BuildRequires: pkgconfig(gobject-introspection-1.0) >= 1.41.0 BuildRequires: pkgconfig(sm)