File gnome-shell-xrdesktop.patch of Package gnome-shell-xrdesktop

diff --git a/meson.build b/meson.build
index d91350ae6..a5dd65421 100644
--- a/meson.build
+++ b/meson.build
@@ -98,6 +98,9 @@ gst_dep = dependency('gstreamer-1.0', version: gst_req, required: false)
 gst_base_dep = dependency('gstreamer-base-1.0', required: false)
 pipewire_dep = dependency('libpipewire-0.3', required: false)
 
+xrdesktop_dep = dependency('xrdesktop-0.16', required: true)
+inputsynth_dep = dependency('libinputsynth-0.16', required: true)
+
 recorder_deps = []
 enable_recorder = gst_dep.found() and gst_base_dep.found() and pipewire_dep.found()
 if enable_recorder
diff --git a/src/main.c b/src/main.c
index 03708fc8f..00fae9bb1 100644
--- a/src/main.c
+++ b/src/main.c
@@ -19,6 +19,9 @@
 #include <meta/prefs.h>
 #include <atk-bridge.h>
 
+#include "shell-vr-mirror.h"
+#include "shell-vr-mirror-dbus.h"
+
 #include "shell-global.h"
 #include "shell-global-private.h"
 #include "shell-perf-log.h"
@@ -533,6 +536,8 @@ main (int argc, char **argv)
   shell_introspection_init ();
   shell_fonts_init ();
 
+  shell_vr_mirror_dbus_init ();
+
   g_log_set_default_handler (default_log_handler, NULL);
 
   /* Initialize the global object */
diff --git a/src/meson.build b/src/meson.build
index 8dd0887c6..841bc3dd4 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -61,7 +61,9 @@ gnome_shell_deps = [
   gi_dep,
   polkit_dep,
   gcr_dep,
-  libsystemd_dep
+  libsystemd_dep,
+  xrdesktop_dep,
+  inputsynth_dep
 ]
 
 gnome_shell_deps += nm_deps
@@ -126,7 +128,9 @@ libshell_private_headers = [
   'shell-app-system-private.h',
   'shell-global-private.h',
   'shell-window-tracker-private.h',
-  'shell-wm-private.h'
+  'shell-wm-private.h',
+  'shell-vr-mirror.h',
+  'shell-vr-mirror-dbus.h'
 ]
 
 libshell_sources = [
@@ -158,7 +162,9 @@ libshell_sources = [
   'shell-window-preview.c',
   'shell-window-preview-layout.c',
   'shell-window-tracker.c',
-  'shell-wm.c'
+  'shell-wm.c',
+  'shell-vr-mirror.c',
+  'shell-vr-mirror-dbus.c'
 ]
 
 if have_networkmanager
diff --git a/src/shell-vr-mirror-dbus.c b/src/shell-vr-mirror-dbus.c
new file mode 100644
index 000000000..261bdbe90
--- /dev/null
+++ b/src/shell-vr-mirror-dbus.c
@@ -0,0 +1,121 @@
+#include "shell-vr-mirror-dbus.h"
+#include "shell-vr-mirror.h"
+
+#define BUS_NAME "org.gnome.Shell.XR"
+static const gchar introspection_xml[] =
+  "<node>"
+  "  <interface name='org.gnome.Shell.XR'>"
+  "    <property name='enabled' type='b' access='readwrite' />"
+  "  </interface>"
+  "</node>";
+
+static void
+_vrshell_set (gboolean enable)
+{
+  ShellVRMirror *vr_mirror = shell_vr_mirror_get_instance ();
+  if (enable)
+    {
+      if (vr_mirror)
+        {
+          g_print ("DBus: Enable XR (already enabled)\n");
+        }
+      else
+        {
+          g_print ("DBus: Enable XR\n");
+          shell_vr_mirror_create_instance ();
+        }
+    }
+  else
+    {
+      g_print ("DBus: Disable XR\n");
+      if (vr_mirror)
+        {
+          shell_vr_mirror_destroy_instance ();
+        }
+    }
+}
+
+static GVariant *
+_handle_dbus_property_get (GDBusConnection *connection,
+                           const gchar *sender,
+                           const gchar *object_path,
+                           const gchar *interface_name,
+                           const gchar *property_name,
+                           GError **error,
+                           gpointer user_data)
+{
+  if (g_strcmp0 (property_name, "enabled") == 0)
+    {
+      ShellVRMirror *instance = shell_vr_mirror_get_instance ();
+      gboolean vr_running = (instance != NULL);
+      GVariant *ret = g_variant_new_boolean (vr_running);
+      return ret;
+    }
+  else
+    {
+      g_print ("Error, only XR enabled property supported!\n");
+      return NULL;
+    }
+}
+
+static gboolean
+_handle_dbus_property_set (GDBusConnection *connection,
+                           const gchar *sender,
+                           const gchar *object_path,
+                           const gchar *interface_name,
+                           const gchar *property_name,
+                           GVariant *value,
+                           GError **error,
+                           gpointer user_data)
+{
+  if (g_strcmp0 (property_name, "enabled") == 0)
+    {
+      if (!g_variant_is_of_type (value, G_VARIANT_TYPE_BOOLEAN))
+        {
+          g_print ("Error, XR enabled property must be bool!\n");
+          return FALSE;
+        }
+      gboolean setTo = g_variant_get_boolean (value);
+      _vrshell_set (setTo);
+      return TRUE;
+    }
+  return FALSE;
+}
+
+static const GDBusInterfaceVTable interface_vtable =
+{
+  NULL,
+  _handle_dbus_property_get,
+  _handle_dbus_property_set
+};
+
+static void
+_on_dbus_bus_aquired (GDBusConnection *connection,
+                      const gchar     *name,
+                      gpointer         user_data)
+{
+  GDBusNodeInfo *introspection_data = g_dbus_node_info_new_for_xml (
+    introspection_xml, NULL);
+
+  g_dbus_connection_register_object (connection,
+                                     "/org/gnome/Shell/XR",
+                                     introspection_data->interfaces[0],
+                                     &interface_vtable,
+                                     NULL,  /* user_data */
+                                     NULL,  /* user_data_free_func */
+                                     NULL); /* GError** */
+}
+
+void
+shell_vr_mirror_dbus_init (void)
+{
+  g_bus_own_name (G_BUS_TYPE_SESSION,
+                  BUS_NAME,
+                  G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT |
+                  G_BUS_NAME_OWNER_FLAGS_REPLACE,
+                  _on_dbus_bus_aquired,
+                  NULL,
+                  NULL,
+                  NULL,
+                  NULL);
+}
diff --git a/src/shell-vr-mirror-dbus.h b/src/shell-vr-mirror-dbus.h
new file mode 100644
index 000000000..1ee4da3c7
--- /dev/null
+++ b/src/shell-vr-mirror-dbus.h
@@ -0,0 +1,3 @@
+
+void
+shell_vr_mirror_dbus_init (void);
diff --git a/src/shell-vr-mirror.c b/src/shell-vr-mirror.c
new file mode 100644
index 000000000..618cde5a2
--- /dev/null
+++ b/src/shell-vr-mirror.c
@@ -0,0 +1,1648 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+#include "config.h"
+
+#include <clutter/clutter.h>
+#include <glib/gprintf.h>
+
+#include <graphene.h>
+#include <glib.h>
+
+#include <inputsynth.h>
+
+#include <meta/meta-shaped-texture.h>
+#include <meta/display.h>
+
+#include "shell-vr-mirror.h"
+#include "shell-global.h"
+
+#include <meta/util.h>
+
+#include <GL/gl.h>
+
+#include <meta/meta-cursor-tracker.h>
+#include <meta/meta-workspace-manager.h>
+
+/* singleton variable for shell_vr_mirror_get_instance() */
+static ShellVRMirror *shell_vr_mirror_instance = NULL;
+
+/* 1 pixel/meter = 0.0254 dpi */
+#define SHELL_VR_PIXELS_PER_METER 720.0f
+#define SHELL_VR_DESKTOP_PLANE_DISTANCE 3.5f
+#define SHELL_VR_LAYER_DISTANCE 0.1f
+
+#define DEFAULT_LEVEL 0.5
+
+static GLenum (*_glGetError) (void);
+static void (*_glFinish) (void);
+static void (*_glFlush) (void);
+static void (*_glGenTextures) (GLsizei, GLuint*);
+static void (*_glDeleteTextures) (GLsizei, const GLuint*);
+static void (*_glTexParameteri) (GLenum, GLenum, GLint);
+static void (*_glBindTexture) (GLenum, GLuint);
+static void (*_glGetTexLevelParameteriv) (GLenum, GLint, GLenum, GLint*);
+static void (*_glCreateMemoryObjectsEXT) (GLsizei, GLuint*);
+static void (*_glDeleteMemoryObjectsEXT) (GLsizei n, const GLuint *memoryObjects);
+
+static void (*_glMemoryObjectParameterivEXT) (GLuint, GLenum, const GLint*);
+static void (*_glGetMemoryObjectParameterivEXT) (GLuint, GLenum, GLint*);
+static void (*_glImportMemoryFdEXT) (GLuint, GLuint64, GLenum, GLint);
+static void (*_glTexStorageMem2DEXT) (GLenum, GLsizei, GLenum, GLsizei,
+                                      GLsizei, GLuint, GLuint64);
+static void (*_glCopyImageSubData) (GLuint, GLenum, GLint, GLint, GLint, GLint,
+                                    GLuint, GLenum, GLint, GLint, GLint, GLint,
+                                    GLsizei, GLsizei, GLsizei);
+
+GLAPI void APIENTRY (*_glNamedFramebufferTexture) (GLuint framebuffer, GLenum attachment, GLuint texture, GLint level);
+GLAPI void APIENTRY (*_glNamedFramebufferTextureLayer) (GLuint framebuffer, GLenum attachment, GLuint texture, GLint level, GLint layer);
+GLAPI void APIENTRY (*_glGenFramebuffers) (GLsizei n, GLuint *framebuffers);
+GLAPI void APIENTRY (*_glDeleteFramebuffers) (GLsizei n, const GLuint *framebuffers);
+GLAPI GLenum APIENTRY (*_glCheckNamedFramebufferStatus) (GLuint framebuffer, GLenum target);
+GLAPI void APIENTRY (*_glFramebufferTexture2D) (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level);
+GLAPI void APIENTRY (*_glBindFramebuffer) (GLenum target, GLuint framebuffer);
+GLAPI void APIENTRY (*_glBlitNamedFramebuffer) (GLuint readFramebuffer, GLuint drawFramebuffer, GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter);
+GLAPI void APIENTRY (*_glCreateFramebuffers) (GLsizei n, GLuint *framebuffers);
+
+static GLubyte *(*_glGetString) (GLenum);
+
+#define ENUM_TO_STR(r) case r: return #r
+
+static const gchar*
+gl_error_string (GLenum code)
+{
+  switch (code)
+    {
+      ENUM_TO_STR(GL_NO_ERROR);
+      ENUM_TO_STR(GL_INVALID_ENUM);
+      ENUM_TO_STR(GL_INVALID_VALUE);
+      ENUM_TO_STR(GL_INVALID_OPERATION);
+      ENUM_TO_STR(GL_INVALID_FRAMEBUFFER_OPERATION);
+      ENUM_TO_STR(GL_OUT_OF_MEMORY);
+      ENUM_TO_STR(GL_STACK_UNDERFLOW);
+      ENUM_TO_STR(GL_STACK_OVERFLOW);
+      default:
+        return "UNKNOWN GL Error";
+    }
+}
+
+static gboolean
+is_grab_op_valid (MetaGrabOp grab_op)
+{
+  switch (grab_op)
+    {
+      case META_GRAB_OP_NONE:
+      case META_GRAB_OP_WAYLAND_POPUP:
+      case META_GRAB_OP_FRAME_BUTTON:
+      case META_GRAB_OP_MOVING:
+      case META_GRAB_OP_RESIZING_NW:
+      case META_GRAB_OP_RESIZING_N:
+      case META_GRAB_OP_RESIZING_NE:
+      case META_GRAB_OP_RESIZING_E:
+      case META_GRAB_OP_RESIZING_SW:
+      case META_GRAB_OP_RESIZING_S:
+      case META_GRAB_OP_RESIZING_SE:
+      case META_GRAB_OP_RESIZING_W:
+      case META_GRAB_OP_KEYBOARD_MOVING:
+      case META_GRAB_OP_KEYBOARD_RESIZING_UNKNOWN:
+      case META_GRAB_OP_KEYBOARD_RESIZING_NW:
+      case META_GRAB_OP_KEYBOARD_RESIZING_N:
+      case META_GRAB_OP_KEYBOARD_RESIZING_NE:
+      case META_GRAB_OP_KEYBOARD_RESIZING_E:
+      case META_GRAB_OP_KEYBOARD_RESIZING_SW:
+      case META_GRAB_OP_KEYBOARD_RESIZING_S:
+      case META_GRAB_OP_KEYBOARD_RESIZING_SE:
+      case META_GRAB_OP_KEYBOARD_RESIZING_W:
+        return TRUE;
+      default:
+        return FALSE;
+    }
+}
+
+
+static void
+gl_check_error (const char* prefix)
+{
+    GLenum err = 0 ;
+    while((err = _glGetError ()) != GL_NO_ERROR)
+    {
+        printf("GL ERROR: %s - %s\n", prefix, gl_error_string (err));
+    }
+}
+
+static const gchar *
+gl_get_vendor (void)
+{
+  static const gchar *vendor = NULL;
+  if (!vendor)
+    vendor = (const gchar *) _glGetString (GL_VENDOR);
+  return vendor;
+}
+
+static gboolean
+_load_gl_symbol (const char *name, void **func)
+{
+  *func = cogl_get_proc_address (name);
+  if (!*func)
+    {
+      g_printerr ("Error: Failed to resolve required GL symbol \"%s\"\n", name);
+      return FALSE;
+    }
+  return TRUE;
+}
+
+static gboolean
+_load_gl_functions (void)
+{
+  if (!_load_gl_symbol ("glCreateMemoryObjectsEXT",
+                        (void **) &_glCreateMemoryObjectsEXT))
+    return FALSE;
+  if (!_load_gl_symbol ("glDeleteMemoryObjectsEXT",
+                        (void **) &_glDeleteMemoryObjectsEXT))
+    return FALSE;
+  if (!_load_gl_symbol ("glMemoryObjectParameterivEXT",
+                        (void **) &_glMemoryObjectParameterivEXT))
+    return FALSE;
+  if (!_load_gl_symbol ("glGetMemoryObjectParameterivEXT",
+                        (void **) &_glGetMemoryObjectParameterivEXT))
+    return FALSE;
+  if (!_load_gl_symbol ("glImportMemoryFdEXT",
+                        (void **) &_glImportMemoryFdEXT))
+    return FALSE;
+  if (!_load_gl_symbol ("glTexStorageMem2DEXT",
+                        (void **) &_glTexStorageMem2DEXT))
+    return FALSE;
+  if (!_load_gl_symbol ("glCopyImageSubData",
+                        (void **) &_glCopyImageSubData))
+    return FALSE;
+  if (!_load_gl_symbol ("glGetTexLevelParameteriv",
+                        (void **) &_glGetTexLevelParameteriv))
+    return FALSE;
+  if (!_load_gl_symbol ("glGenTextures",
+                        (void **) &_glGenTextures))
+    return FALSE;
+  if (!_load_gl_symbol ("glDeleteTextures",
+                        (void **) &_glDeleteTextures))
+    return FALSE;
+  if (!_load_gl_symbol ("glTexParameteri",
+                        (void **) &_glTexParameteri))
+    return FALSE;
+  if (!_load_gl_symbol ("glBindTexture",
+                        (void **) &_glBindTexture))
+    return FALSE;
+  if (!_load_gl_symbol ("glFinish",
+                        (void **) &_glFinish))
+  if (!_load_gl_symbol ("glFlush",
+                        (void **) &_glFlush))
+    return FALSE;
+  if (!_load_gl_symbol ("glGetError",
+                        (void **) &_glGetError))
+    return FALSE;
+  if (!_load_gl_symbol ("glGetString",
+                        (void **) &_glGetString))
+    return FALSE;
+
+  if (!_load_gl_symbol ("glNamedFramebufferTexture",
+                        (void **) &_glNamedFramebufferTexture))
+    return FALSE;
+  if (!_load_gl_symbol ("glNamedFramebufferTextureLayer",
+                        (void **) &_glNamedFramebufferTextureLayer))
+    return FALSE;
+  if (!_load_gl_symbol ("glGenFramebuffers",
+                        (void **) &_glGenFramebuffers))
+    return FALSE;
+  if (!_load_gl_symbol ("glDeleteFramebuffers",
+                        (void **) &_glDeleteFramebuffers))
+    return FALSE;
+  if (!_load_gl_symbol ("glCheckNamedFramebufferStatus",
+                        (void **) &_glCheckNamedFramebufferStatus))
+    return FALSE;
+  if (!_load_gl_symbol ("glFramebufferTexture2D",
+                        (void **) &_glFramebufferTexture2D))
+    return FALSE;
+  if (!_load_gl_symbol ("glBindFramebuffer",
+                        (void **) &_glBindFramebuffer))
+    return FALSE;
+  if (!_load_gl_symbol ("glBlitNamedFramebuffer",
+                        (void **) &_glBlitNamedFramebuffer))
+    return FALSE;
+  if (!_load_gl_symbol ("glCreateFramebuffers",
+                        (void **) &_glCreateFramebuffers))
+    return FALSE;
+
+  return TRUE;
+}
+
+struct _ShellVRMirror
+{
+  GObject parent;
+
+  InputSynth *vr_input;
+
+  /* either overlay or scene client */
+  XrdShell *client;
+
+  MetaCursorTracker *cursor_tracker;
+  GLuint cursor_gl_texture;
+  /* A framebuffer that cursor_gl_texture is bound to */
+  GLuint cursor_fb;
+  /* A framebuffer that cursor_gl_texture is bound to */
+  GLuint shell_cursor_fb;
+
+  uint32_t top_layer;
+
+  gboolean nvidia;
+
+  GSList *grabbed_windows;
+
+  gboolean shutdown;
+
+  bool rendering;
+  bool framecycle;
+};
+
+struct _ShellVREffect {
+  ClutterEffect parent_instance;
+
+  ClutterActor *actor;
+  XrdWindow *xrd_win;
+};
+
+G_DEFINE_TYPE (ShellVREffect, shell_vr_effect, CLUTTER_TYPE_EFFECT);
+
+typedef struct
+{
+  MetaWindowActor *meta_window_actor;
+
+  bool keep_above_restore;
+  bool keep_below_restore;
+
+  /* The offscreen texture gnome shell renders into to avoid allocating a
+   * new offscreen texture every frame */
+  GLuint gl_texture;
+
+  /* A framebuffer that gl_texture is bound to */
+  GLuint fb;
+
+  /* A framebuffer that the gnome-shell window texture is bound to */
+  GLuint shell_win_fb;
+} ShellVRWindow;
+
+G_DEFINE_TYPE (ShellVRMirror, shell_vr_mirror, G_TYPE_OBJECT);
+
+
+
+static void
+shell_vr_effect_init (ShellVREffect *self)
+{
+  self->actor = NULL;
+  self->xrd_win = NULL;
+}
+
+static void
+shell_vr_effect_set_actor (ClutterActorMeta *meta,
+                           ClutterActor     *actor)
+{
+  ShellVREffect *self = SHELL_VR_EFFECT (meta);
+
+  // Maintain a pointer to the actor
+  self->actor = actor;
+
+  // If we've been detached by the actor then we should just bail out here
+  if (self->actor == NULL)
+    return;
+}
+
+static gboolean
+_upload_window (ShellVRMirror *self, XrdWindow *xrd_win);
+
+
+static void
+shell_vr_effect_paint (ClutterEffect           *effect,
+                       ClutterPaintNode        *node,
+                       ClutterPaintContext     *paint_context,
+                       ClutterEffectPaintFlags  flags)
+{
+  ShellVREffect *self = SHELL_VR_EFFECT (effect);
+  if (!self->actor || !CLUTTER_IS_ACTOR(self->actor))
+    return;
+
+  clutter_actor_continue_paint (self->actor, paint_context);
+
+  if (!(flags & CLUTTER_EFFECT_PAINT_ACTOR_DIRTY))
+    return;
+
+  ShellVRMirror *mirror = shell_vr_mirror_get_instance ();
+  if (!mirror || !self->xrd_win)
+    return;
+  _upload_window (mirror, self->xrd_win);
+}
+
+
+static void
+shell_vr_effect_pick (ClutterEffect      *effect,
+                      ClutterPickContext *pick_context)
+{
+  ShellVREffect *self = SHELL_VR_EFFECT (effect);
+  if (self->actor == NULL || !CLUTTER_IS_ACTOR(self->actor))
+    return;
+
+  clutter_actor_continue_pick (self->actor, pick_context);
+}
+
+static void
+shell_vr_effect_class_init (ShellVREffectClass *klass)
+{
+  ClutterActorMetaClass *meta_class = CLUTTER_ACTOR_META_CLASS (klass);
+
+  meta_class->set_actor = shell_vr_effect_set_actor;
+
+  ClutterEffectClass *effect_class = CLUTTER_EFFECT_CLASS (klass);
+  effect_class->paint = shell_vr_effect_paint;
+  effect_class->pick = shell_vr_effect_pick;
+}
+
+static GLint
+to_sized_format(GLint format)
+{
+  if (format == GL_RGBA8)
+    return GL_RGBA8;
+
+  if (format == GL_RGB)
+    return GL_RGB8;
+  if (format == GL_RGBA)
+    return GL_RGBA8;
+  if (format == GL_BGR)
+    return GL_RGBA8;
+
+  g_printerr("Format 0x%x not implemented, using GL_RGBA8\n", format);
+  return GL_RGBA8;
+}
+
+static GulkanTexture*
+_allocate_external_memory (ShellVRMirror *self,
+                           GulkanClient  *client,
+                           GLuint         source_gl_handle,
+                           GLenum         gl_target,
+                           uint32_t       width,
+                           uint32_t       height,
+                           GLuint        *out_gl_handle)
+{
+  g_print ("Reallocating %dx%d vulkan texture\n", width, height);
+
+  /* Get meta texture format */
+  _glBindTexture (gl_target, source_gl_handle);
+  GLint internal_format;
+  _glGetTexLevelParameteriv (GL_TEXTURE_2D, 0,
+                             GL_TEXTURE_INTERNAL_FORMAT, &internal_format);
+
+  internal_format = to_sized_format (internal_format);
+
+  gsize size;
+  int fd;
+
+  VkExtent2D extent = { width, height };
+
+  G3kContext *g3k = xrd_shell_get_g3k (self->client);
+  VkImageLayout layout = g3k_context_get_upload_layout (g3k);
+  GulkanTexture *texture =
+    gulkan_texture_new_export_fd (client, extent, VK_FORMAT_R8G8B8A8_SRGB,
+                                  layout, &size, &fd);
+  if (texture == NULL)
+    {
+      g_printerr ("Error: Unable to initialize Vulkan texture.\n");
+      return NULL;
+    }
+
+  GLint gl_dedicated_mem = GL_TRUE;
+  GLuint gl_mem_object;
+  _glCreateMemoryObjectsEXT (1, &gl_mem_object);
+  gl_check_error ("_glCreateMemoryObjectsEXT");
+
+  _glMemoryObjectParameterivEXT (gl_mem_object,
+                                 GL_DEDICATED_MEMORY_OBJECT_EXT,
+                                &gl_dedicated_mem);
+  gl_check_error ("_glMemoryObjectParameterivEXT");
+
+  _glGetMemoryObjectParameterivEXT (gl_mem_object,
+                                    GL_DEDICATED_MEMORY_OBJECT_EXT,
+                                   &gl_dedicated_mem);
+  gl_check_error ("_glGetMemoryObjectParameterivEXT");
+
+  _glImportMemoryFdEXT (gl_mem_object, size,
+                        GL_HANDLE_TYPE_OPAQUE_FD_EXT, fd);
+  gl_check_error ("_glImportMemoryFdEXT");
+
+  _glGenTextures (1, out_gl_handle);
+  gl_check_error ("_glGenTextures");
+
+  _glBindTexture (GL_TEXTURE_2D, *out_gl_handle);
+  gl_check_error ("_glBindTexture");
+
+  _glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_TILING_EXT, GL_OPTIMAL_TILING_EXT);
+  gl_check_error ("glTexParameteri GL_TEXTURE_TILING_EXT");
+  _glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+  gl_check_error ("glTexParameteri GL_TEXTURE_MIN_FILTER");
+  _glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+  gl_check_error ("glTexParameteri GL_TEXTURE_MAG_FILTER");
+
+  if (self->nvidia)
+    internal_format = GL_RGBA8;
+
+  _glTexStorageMem2DEXT (GL_TEXTURE_2D, 1, internal_format,
+                         width, height, gl_mem_object, 0);
+  gl_check_error ("_glTexStorageMem2DEXT");
+
+  _glFinish ();
+
+  _glDeleteMemoryObjectsEXT (1, &gl_mem_object);
+  gl_check_error ("_glDeleteMemoryObjectsEXT");
+
+  return texture;
+}
+
+
+static void
+_cursor_changed_cb (MetaCursorTracker *cursor_tracker,
+                    gpointer          _self)
+{
+  ShellVRMirror *self = _self;
+  if (!self)
+    return;
+
+  CoglTexture *cogl_texture = meta_cursor_tracker_get_sprite (cursor_tracker);
+  int hotspot_x, hotspot_y;
+  meta_cursor_tracker_get_hot (cursor_tracker, &hotspot_x, &hotspot_y);
+
+
+  if (cogl_texture == NULL || !cogl_is_texture (cogl_texture))
+    {
+      g_printerr ("Cursor Error: Could not CoglTexture.\n");
+      return;
+    }
+
+  GLuint meta_tex;
+  GLenum meta_target;
+  if (!cogl_texture_get_gl_texture (cogl_texture, &meta_tex, &meta_target))
+    {
+      g_printerr ("Cursor Error: Could not get GL handle.\n");
+      return;
+    }
+
+  GulkanClient *gulkan_client = xrd_shell_get_gulkan (self->client);
+
+  guint cursor_width = (guint) cogl_texture_get_width (cogl_texture);
+  guint cursor_height = (guint) cogl_texture_get_width (cogl_texture);
+
+  G3kCursor *cursor = xrd_shell_get_desktop_cursor (self->client);
+  g3k_cursor_set_hotspot (cursor, hotspot_x, hotspot_y);
+
+  GulkanTexture *texture = g3k_cursor_get_texture (cursor);
+
+  gboolean extent_changed = TRUE;
+  if (texture)
+    {
+      VkExtent2D extent = gulkan_texture_get_extent (texture);
+      extent_changed = (cursor_width != extent.width ||
+                        cursor_height != extent.height);
+    }
+
+  if (extent_changed)
+    {
+      if (self->cursor_gl_texture != 0)
+        {
+          _glDeleteTextures (1, &self->cursor_gl_texture);
+          _glDeleteFramebuffers (1, &self->cursor_fb);
+          _glDeleteFramebuffers (1, &self->shell_cursor_fb);
+        }
+
+      g_print ("Cursor: Reallocating %dx%d vulkan texture\n",
+               cursor_width, cursor_height);
+
+      texture =
+        _allocate_external_memory (self, gulkan_client, meta_tex, meta_target,
+                                   cursor_width, cursor_height,
+                                  &self->cursor_gl_texture);
+        _glCreateFramebuffers(1, &self->cursor_fb);
+        gl_check_error ("glCreateFramebuffers");
+        _glNamedFramebufferTexture(self->cursor_fb, GL_COLOR_ATTACHMENT0, self->cursor_gl_texture, 0);
+        gl_check_error ("glNamedFramebufferTexture");
+
+        _glCreateFramebuffers(1, &self->shell_cursor_fb);
+        gl_check_error ("glCreateFramebuffers");
+        _glNamedFramebufferTexture(self->shell_cursor_fb, GL_COLOR_ATTACHMENT0, meta_tex, 0);
+        gl_check_error ("glNamedFramebufferTexture");
+    }
+
+  /*
+   * gnome-shell might not reuse the same texture for its cursor.
+   * Could be micro optimized by managing n fbos with their textures bound per window.
+   */
+  _glNamedFramebufferTexture(self->shell_cursor_fb, GL_COLOR_ATTACHMENT0, meta_tex, 0);
+  gl_check_error ("glNamedFramebufferTexture");
+
+  VkExtent2D extent = gulkan_texture_get_extent (texture);
+  _glBlitNamedFramebuffer((GLuint)self->shell_cursor_fb,             // readFramebuffer
+                          (GLuint)self->cursor_fb,                       // backbuffer     // drawFramebuffer
+                          (GLint)0,                        // srcX0
+                          (GLint)0,                        // srcY0
+                          (GLint)extent.width,                        // srcX1
+                          (GLint)extent.height,                        // srcY1
+                          (GLint)0,                        // dstX0
+                          (GLint)0,                        // dstY0
+                          (GLint)extent.width,                    // dstX1
+                          (GLint)extent.height,                    // dstY1
+                          (GLbitfield)GL_COLOR_BUFFER_BIT, // mask
+                          (GLenum)GL_LINEAR);              // filter
+
+  _glFinish ();
+
+  if (extent_changed)
+    {
+      g3k_cursor_set_and_submit_texture (cursor, texture);
+    }
+}
+
+static void
+_grab_op_begin_cb (MetaDisplay *display1,
+                   MetaDisplay *display2,
+                   MetaWindow  *grab_window,
+                   MetaGrabOp   op,
+                   gpointer     _self)
+{
+  // yes, this does happen
+  if (!grab_window)
+    return;
+
+  if (!is_grab_op_valid(op))
+    {
+      g_warning("_grab_op_begin_cb: Invalid grab op %d received.\n", op);
+      return;
+    }
+
+  ShellVRMirror *self = _self;
+  if (!g_slist_find (self->grabbed_windows, grab_window))
+    self->grabbed_windows = g_slist_append (self->grabbed_windows, grab_window);
+
+  g_debug ("Start grab window %s\n", meta_window_get_title (grab_window));
+}
+
+static void
+_grab_op_end_cb (MetaDisplay *display1,
+                 MetaDisplay *display2,
+                 MetaWindow  *grab_window,
+                 MetaGrabOp   op,
+                 gpointer     _self)
+{
+  if (!grab_window)
+    return;
+
+  if (!is_grab_op_valid(op))
+    {
+      g_warning("_grab_op_end_cb: Invalid grab op %d received.\n", op);
+      return;
+    }
+
+  ShellVRMirror *self = _self;
+  self->grabbed_windows = g_slist_remove (self->grabbed_windows, grab_window);
+
+  g_debug ("End Grab window %s\n", meta_window_get_title (grab_window));
+}
+
+static XrdWindow *
+_meta_win_to_xrd_window (ShellVRMirror *self, MetaWindow *meta_window)
+{
+  XrdWindow *window = xrd_shell_lookup_window (self->client, meta_window);
+  return window;
+}
+
+static void
+shell_vr_mirror_init (ShellVRMirror *self)
+{
+  self->top_layer = 0;
+  self->vr_input = NULL;
+  self->client = NULL;
+  self->shutdown = FALSE;
+}
+
+ShellVRMirror *
+shell_vr_mirror_new (void)
+{
+  return g_object_new (SHELL_TYPE_VR_MIRROR, NULL);
+}
+
+ShellVRMirror *
+shell_vr_mirror_get_instance (void)
+{
+  return shell_vr_mirror_instance;
+}
+
+ShellVRMirror *
+shell_vr_mirror_create_instance (void)
+{
+  shell_vr_mirror_instance = shell_vr_mirror_new ();
+  shell_vr_mirror_initialize (shell_vr_mirror_instance);
+  return shell_vr_mirror_instance;
+}
+
+/* Coordinate space: 0 == x: left, y == 0: top */
+static graphene_point_t
+_window_to_desktop_coords (MetaWindow       *meta_win,
+                           graphene_point_t *window_pixels)
+ {
+  MetaRectangle window_rect;
+  meta_window_get_buffer_rect (meta_win, &window_rect);
+
+  graphene_point_t desktop_coords = {
+    .x = window_rect.x + window_pixels->x,
+    .y = window_rect.y + window_pixels->y
+  };
+  return desktop_coords;
+}
+
+static MetaWindow *
+_get_validated_window (MetaWindowActor *actor)
+{
+  if (actor == NULL || !META_IS_WINDOW_ACTOR (actor))
+    {
+      g_printerr ("Error: Actor for move cursor not available.\n");
+      return NULL;
+    }
+
+  MetaWindow *meta_win = meta_window_actor_get_meta_window (actor);
+  if (meta_win == NULL || !META_IS_WINDOW (meta_win))
+    {
+      g_printerr ("Error: No window to move\n");
+      return NULL;
+    }
+
+  if (meta_window_get_display (meta_win) == NULL)
+    {
+      g_printerr ("Error: window has no display?!\n");
+      return NULL;
+    }
+
+  return meta_win;
+}
+
+static void
+_ensure_focused (MetaWindow *meta_win)
+{
+  /* mutter asserts that we don't mess with override_redirect windows */
+  if (meta_window_is_override_redirect (meta_win))
+    return;
+
+  meta_window_raise (meta_win);
+  if (!meta_window_has_focus (meta_win))
+    meta_window_focus (meta_win, CurrentTime);
+}
+
+static void
+_ensure_on_workspace (MetaWindow *meta_win)
+{
+  if (meta_window_is_on_all_workspaces (meta_win))
+    return;
+
+  MetaDisplay *display = meta_window_get_display (meta_win);
+
+  MetaWorkspaceManager *manager = meta_display_get_workspace_manager (display);
+  MetaWorkspace *ws_current =
+    meta_workspace_manager_get_active_workspace (manager);
+
+  MetaWorkspace *ws_window = meta_window_get_workspace (meta_win);
+  if (!ws_window || !META_IS_WORKSPACE (ws_window))
+    return;
+
+  if (ws_current == ws_window)
+    return;
+
+  guint32 timestamp = meta_display_get_current_time_roundtrip (display);
+  meta_workspace_activate_with_focus (ws_window, meta_win, timestamp);
+}
+
+static void
+_click_cb (XrdShell      *client,
+           XrdClickEvent *event,
+           ShellVRMirror *self)
+{
+  ShellVRWindow *shell_win;
+  g_object_get (event->window, "native", &shell_win, NULL);
+  if (!shell_win)
+    return;
+
+  MetaWindowActor *actor = (MetaWindowActor*) shell_win->meta_window_actor;
+  MetaWindow *meta_win = _get_validated_window (actor);
+  if (!meta_win)
+    return;
+
+  _ensure_on_workspace (meta_win);
+  _ensure_focused (meta_win);
+
+  graphene_point_t desktop_coords =
+    _window_to_desktop_coords (meta_win, event->position);
+
+  input_synth_click (self->vr_input,
+                     desktop_coords.x, desktop_coords.y,
+                     event->button, event->state);
+}
+
+static void
+_move_cursor_cb (XrdShell           *client,
+                 XrdMoveCursorEvent *event,
+                 ShellVRMirror      *self)
+{
+  ShellVRWindow *shell_win;
+  g_object_get (event->window, "native", &shell_win, NULL);
+  if (!shell_win)
+    return;
+
+  MetaWindowActor *actor = (MetaWindowActor*) shell_win->meta_window_actor;
+  MetaWindow *meta_win = _get_validated_window (actor);
+  if (!meta_win)
+    return;
+
+  /* do not move mouse cursor while the window is grabbed (in "move" mode) */
+  if (g_slist_find (self->grabbed_windows, meta_win))
+    return;
+
+  _ensure_on_workspace (meta_win);
+  _ensure_focused (meta_win);
+
+  graphene_point_t desktop_coords =
+    _window_to_desktop_coords (meta_win, event->position);
+  input_synth_move_cursor (self->vr_input, desktop_coords.x, desktop_coords.y);
+}
+
+static void
+_keyboard_press_cb (XrdShell      *client,
+                    G3kKeyEvent   *event,
+                    ShellVRMirror *self)
+{
+  XrdWindow* keyboard_xrd_win = xrd_shell_get_keyboard_window (client);
+  if (!keyboard_xrd_win)
+    {
+      g_print ("ERROR: No keyboard window!\n");
+      return;
+    }
+
+  ShellVRWindow *shell_win;
+  g_object_get (keyboard_xrd_win, "native", &shell_win, NULL);
+  if (!shell_win)
+    return;
+
+  MetaWindowActor *actor = (MetaWindowActor*) shell_win->meta_window_actor;
+  MetaWindow *meta_win = _get_validated_window (actor);
+  if (!meta_win)
+    return;
+
+  _ensure_on_workspace (meta_win);
+  _ensure_focused (meta_win);
+
+  if (event->keyval == 0)
+    {
+      input_synth_characters (self->vr_input, event->string);
+    }
+  else
+    {
+      // TODO: keyval input
+    }
+}
+
+static void
+_disconnect_signals (ShellVRMirror *self);
+static void
+_connect_signals (ShellVRMirror *self);
+
+static void
+_destroy_textures(ShellVRMirror *self)
+{
+  XrdShell *client = self->client;
+  GSList *windows = xrd_shell_get_windows(client);
+  for (GSList *l = windows; l != NULL; l = l->next) {
+    XrdWindow *xrd_win = (XrdWindow *) l->data;
+
+    ShellVRWindow *shell_win;
+    g_object_get (xrd_win, "native", &shell_win, NULL);
+
+    // external memory texture, has to be recreated
+    _glDeleteTextures (1, &shell_win->gl_texture);
+    shell_win->gl_texture = 0;
+  }
+
+  _glDeleteTextures (1, &self->cursor_gl_texture);
+  self->cursor_gl_texture = 0;
+}
+
+static void
+_detach_paint_cbs (XrdShell *client)
+{
+  GSList *windows = xrd_shell_get_windows (client);
+  for (GSList *l = windows; l != NULL; l = l->next)
+    {
+      XrdWindow *xrd_win = (XrdWindow *) l->data;
+
+      ShellVRWindow *shell_win;
+      g_object_get (xrd_win, "native", &shell_win, NULL);
+      clutter_actor_clear_effects (CLUTTER_ACTOR(shell_win->meta_window_actor));
+    }
+}
+
+static void
+_state_change_cb (XrdShell            *xrd_client,
+                  GxrStateChangeEvent *event,
+                  gpointer             _self)
+{
+  g_print ("Handling VR state change event\n");
+
+  ShellVRMirror *self = SHELL_VR_MIRROR (_self);
+
+  switch (event->state_change)
+  {
+    case GXR_STATE_SHUTDOWN:
+    {
+      g_print("State change event: VR Shutdown\n");
+      _detach_paint_cbs (xrd_client);
+      ShellVRMirror *self = SHELL_VR_MIRROR (_self);
+      _destroy_textures(self);
+      shell_vr_mirror_destroy_instance ();
+    } break;
+    case GXR_STATE_FRAMECYCLE_START:
+      self->framecycle = TRUE;
+      g_debug ("State change event: frame cycle started");
+      break;
+    case GXR_STATE_FRAMECYCLE_STOP:
+      self->framecycle = FALSE;
+      g_debug ("State change event: frame cycle stopped");
+      break;
+    case GXR_STATE_RENDERING_START:
+      self->rendering = TRUE;
+      g_debug ("State change event: rendering started");
+      break;
+    case GXR_STATE_RENDERING_STOP:
+      self->rendering = TRUE;
+      g_debug ("State change event: rendering stopped");
+      break;
+  }
+}
+
+static void
+_init_input (ShellVRMirror *self)
+{
+  if (meta_is_wayland_compositor ())
+    self->vr_input = input_synth_new (INPUTSYNTH_BACKEND_WAYLAND_CLUTTER);
+  else
+    self->vr_input = input_synth_new (INPUTSYNTH_BACKEND_XDO);
+}
+
+static gboolean
+_upload_gl_external_memory (ShellVRMirror     *self,
+                            GulkanClient      *client,
+                            XrdWindow         *xrd_win,
+                            MetaShapedTexture *mst,
+                            MetaRectangle     *rect)
+{
+  CoglTexture *cogl_texture = meta_shaped_texture_get_texture (mst);
+
+  if (cogl_texture == NULL || !cogl_is_texture (cogl_texture))
+    {
+      g_printerr ("Error: Could not CoglTexture from MetaShapedTexture.\n");
+      return FALSE;
+    }
+
+  GLuint meta_tex;
+  GLenum meta_target;
+  if (!cogl_texture_get_gl_texture (cogl_texture, &meta_tex, &meta_target))
+    {
+      g_printerr ("Error: Could not get GL handle from CoglTexture.\n");
+      return FALSE;
+    }
+
+  ShellVRWindow *shell_win;
+  g_object_get (xrd_win, "native", &shell_win, NULL);
+  if (!shell_win)
+    return FALSE;
+
+  GulkanTexture *texture = xrd_window_get_texture (xrd_win);
+
+  gboolean extent_changed = TRUE;
+  if (texture)
+    {
+      VkExtent2D extent = gulkan_texture_get_extent (texture);
+      extent_changed = ((guint) rect->width != extent.width ||
+                        (guint) rect->height != extent.height);
+    }
+
+
+  g3k_render_lock ();
+  if (extent_changed)
+    {
+      if (shell_win->gl_texture != 0)
+        {
+          _glDeleteFramebuffers(1, &shell_win->fb);
+          _glDeleteFramebuffers(1, &shell_win->shell_win_fb);
+          _glDeleteTextures (1, &shell_win->gl_texture);
+        }
+
+      texture = _allocate_external_memory (self, client, meta_tex, meta_target,
+                                           rect->width, rect->height,
+                                          &shell_win->gl_texture);
+      _glCreateFramebuffers(1, &shell_win->fb);
+      gl_check_error ("glCreateFramebuffers");
+      _glNamedFramebufferTexture(shell_win->fb, GL_COLOR_ATTACHMENT0, shell_win->gl_texture, 0);
+      gl_check_error ("glNamedFramebufferTexture");
+
+      GLenum comp = _glCheckNamedFramebufferStatus(shell_win->fb, GL_FRAMEBUFFER);
+      if (comp != GL_FRAMEBUFFER_COMPLETE)
+        {
+          g_printerr("xrdesktop fbo incomplete: %d\n", comp);
+        }
+
+
+      _glCreateFramebuffers(1, &shell_win->shell_win_fb);
+      gl_check_error ("glCreateFramebuffers");
+      _glNamedFramebufferTexture(shell_win->shell_win_fb, GL_COLOR_ATTACHMENT0, meta_tex, 0);
+      gl_check_error ("glNamedFramebufferTexture");
+
+     if (!GULKAN_IS_TEXTURE (texture))
+        {
+          g_printerr ("Error creating texture for window!\n");
+          g3k_render_unlock ();
+          return FALSE;
+        }
+    }
+
+  _glFinish ();
+
+  /*
+   * gnome-shell might not reuse the same texture for its window.
+   * Could be micro optimized by managing n fbos with their textures bound per window.
+   */
+  _glNamedFramebufferTexture(shell_win->shell_win_fb, GL_COLOR_ATTACHMENT0, meta_tex, 0);
+  gl_check_error ("glNamedFramebufferTexture");
+
+  VkExtent2D extent = gulkan_texture_get_extent (texture);
+  _glBlitNamedFramebuffer((GLuint)shell_win->shell_win_fb,             // readFramebuffer
+                          (GLuint)shell_win->fb,                       // backbuffer     // drawFramebuffer
+                          (GLint)0,                        // srcX0
+                          (GLint)0,                        // srcY0
+                          (GLint)extent.width,                        // srcX1
+                          (GLint)extent.height,                        // srcY1
+                          (GLint)0,                        // dstX0
+                          (GLint)0,                        // dstY0
+                          (GLint)extent.width,                    // dstX1
+                          (GLint)extent.height,                    // dstY1
+                          (GLbitfield)GL_COLOR_BUFFER_BIT, // mask
+                          (GLenum)GL_LINEAR);              // filter
+
+  _glFinish ();
+
+  if (extent_changed)
+    {
+      struct XrdWindowRect xrd_rect = {
+        .bl = {
+          .x = 0,
+          .y = 0,
+        },
+        .tr = {
+          .x = (uint32_t) rect->width,
+          .y = (uint32_t) rect->height,
+        }
+      };
+      xrd_window_set_and_submit_texture (xrd_win, texture, &xrd_rect);
+    }
+
+  g3k_render_unlock ();
+
+  return TRUE;
+}
+
+static gboolean
+_upload_raw_cairo (ShellVRMirror     *self,
+                   GulkanClient      *client,
+                   XrdWindow         *xrd_win,
+                   MetaShapedTexture *mst,
+                   MetaRectangle     *rect)
+{
+  cairo_rectangle_int_t cairo_rect = {
+    .x = 0,
+    .y = 0,
+    .width = rect->width,
+    .height = rect->height
+  };
+
+  cairo_surface_t *sf = meta_shaped_texture_get_image (mst, &cairo_rect);
+  if (sf == NULL)
+    {
+      g_printerr ("Error: Could not get Cairo surface"
+                  " from MetaShapedTexture.\n");
+      return FALSE;
+    }
+
+  ShellVRWindow *shell_win;
+  g_object_get (xrd_win, "native", &shell_win, NULL);
+  if (!shell_win)
+    return FALSE;
+
+  G3kContext *g3k = xrd_shell_get_g3k (self->client);
+  VkImageLayout upload_layout = g3k_context_get_upload_layout (g3k);
+
+  int texture_width;
+  int texture_height;
+  g_object_get (xrd_win, "texture-width", &texture_width, NULL);
+  g_object_get (xrd_win, "texture_height", &texture_height, NULL);
+
+  GulkanTexture *texture = xrd_window_get_texture (xrd_win);
+
+  g3k_render_lock ();
+  if (rect->width != texture_width ||
+      rect->height != texture_height ||
+      texture == NULL)
+    {
+      g_print ("Reallocating %dx%d vulkan texture\n",
+               rect->width, rect->height);
+      texture =
+        gulkan_texture_new_from_cairo_surface (client, sf,
+                                               VK_FORMAT_B8G8R8A8_SRGB,
+                                               upload_layout);
+
+     if (!GULKAN_IS_TEXTURE (texture))
+        {
+          g_printerr ("Error creating texture for window!\n");
+          g3k_render_unlock ();
+          return FALSE;
+        }
+
+      struct XrdWindowRect xrd_rect = {
+        .bl = {
+          .x = 0,
+          .y = 0,
+        },
+        .tr = {
+          .x = (uint32_t) rect->width,
+          .y = (uint32_t) rect->height,
+        }
+      };
+
+      xrd_window_set_and_submit_texture (xrd_win, texture, &xrd_rect);
+    }
+  else
+    {
+      gulkan_texture_upload_cairo_surface (texture, sf, upload_layout);
+    }
+  g3k_render_unlock ();
+
+  cairo_surface_destroy (sf);
+
+  return TRUE;
+}
+
+static gboolean
+_upload_window (ShellVRMirror *self, XrdWindow *xrd_win)
+{
+  ShellVRWindow *shell_win;
+  g_object_get (xrd_win, "native", &shell_win, NULL);
+  if (!shell_win)
+    return FALSE;
+
+  MetaWindowActor* actor = shell_win->meta_window_actor;
+
+  MetaWindow *meta_win = _get_validated_window (actor);
+  MetaRectangle rect;
+  meta_window_get_buffer_rect (meta_win, &rect);
+
+  /* skip upload of small buffers */
+  if (rect.width <= 10 && rect.height <= 10)
+    return FALSE;
+
+  MetaShapedTexture* mst =
+    (MetaShapedTexture*) meta_window_actor_get_texture (actor);
+
+  GulkanClient *gulkan_client = xrd_shell_get_gulkan (self->client);
+
+  CoglTextureComponents components;
+  if (self->nvidia)
+    {
+      CoglTexture *cogl_texture = meta_shaped_texture_get_texture (mst);
+      if (cogl_texture == NULL || !cogl_is_texture (cogl_texture))
+        {
+          g_printerr ("Error: Could not CoglTexture from MetaShapedTexture.\n");
+          return FALSE;
+        }
+      components = cogl_texture_get_components (cogl_texture);
+    }
+
+  static GMutex upload_mutex;
+  g_mutex_lock (&upload_mutex);
+
+  gboolean ret;
+  /* Use cairo upload as fallback on NVIDIA for RGB buffers */
+  if (self->nvidia && components == COGL_TEXTURE_COMPONENTS_RGB)
+    ret = _upload_raw_cairo (self, gulkan_client, xrd_win, mst, &rect);
+  else
+    ret = _upload_gl_external_memory (self, gulkan_client, xrd_win, mst, &rect);
+
+  g_mutex_unlock (&upload_mutex);
+
+  return ret;
+}
+
+static gboolean
+_is_excluded_from_mirroring (MetaWindow *meta_win)
+{
+  MetaWindowType type = meta_window_get_window_type (meta_win);
+  return
+    type == META_WINDOW_DESKTOP ||
+    type == META_WINDOW_DOCK ||
+    type == META_WINDOW_DND;
+}
+
+static void
+_mirror_current_windows (ShellVRMirror *self)
+{
+  ShellGlobal *global = shell_global_get ();
+  GList *window = shell_global_get_window_actors (global);
+  while (window != NULL)
+    {
+      MetaWindowActor *actor = window->data;
+      shell_vr_mirror_map_actor (self, actor);
+      window = window->next;
+    }
+}
+
+static void
+_apply_desktop_position (MetaWindow *meta_win,
+                         XrdWindow  *xrd_win,
+                         uint32_t    layer)
+{
+  ShellGlobal *global = shell_global_get ();
+  MetaDisplay *display = shell_global_get_display (global);
+
+  int screen_w, screen_h;
+  meta_display_get_size (display, &screen_w, &screen_h);
+
+  MetaRectangle rect;
+  meta_window_get_buffer_rect (meta_win, &rect);
+
+  float x =            rect.x - screen_w / 2.0f + rect.width  / 2.0f;
+  float y = screen_h - rect.y - screen_h / 4.0f - rect.height / 2.0f;
+
+  graphene_point3d_t point = {
+    .x = x / SHELL_VR_PIXELS_PER_METER,
+    .y = y / SHELL_VR_PIXELS_PER_METER + DEFAULT_LEVEL,
+    .z = -SHELL_VR_DESKTOP_PLANE_DISTANCE + SHELL_VR_LAYER_DISTANCE * layer
+  };
+
+  graphene_matrix_t transform;
+  graphene_matrix_init_translate (&transform, &point);
+
+  g3k_object_set_global_transform (G3K_OBJECT (xrd_win), &transform);
+  xrd_window_save_reset_transformation (xrd_win);
+}
+
+static void
+_arrange_windows_by_desktop_position (ShellVRMirror *self)
+{
+  GSList *xrd_win_list = xrd_shell_get_windows (self->client);
+
+  GSList *meta_win_list = NULL;
+
+  for (; xrd_win_list; xrd_win_list = xrd_win_list->next)
+    {
+      XrdWindow *xrd_win = xrd_win_list->data;
+      ShellVRWindow *shell_win;
+      g_object_get (xrd_win, "native", &shell_win, NULL);
+      if (!shell_win)
+        continue;
+
+      MetaWindow *meta_win =
+        meta_window_actor_get_meta_window (shell_win->meta_window_actor);
+
+      if (!_is_excluded_from_mirroring (meta_win))
+        meta_win_list = g_slist_append (meta_win_list, meta_win);
+    }
+
+  ShellGlobal *global = shell_global_get ();
+  MetaDisplay *display = shell_global_get_display (global);
+
+  GSList *sorted_windows =
+    meta_display_sort_windows_by_stacking (display, meta_win_list);
+
+  for (self->top_layer = 0; sorted_windows;
+       sorted_windows = sorted_windows->next)
+    {
+      XrdWindow *xrd_window = _meta_win_to_xrd_window (self,
+                                                       sorted_windows->data);
+      if (!xrd_window)
+        continue;
+
+      _apply_desktop_position (sorted_windows->data,
+                               xrd_window, self->top_layer);
+      self->top_layer++;
+    }
+  g_slist_free (sorted_windows);
+}
+
+static void
+_connect_signals (ShellVRMirror *self)
+{
+  g_signal_connect (self->client, "keyboard-press-event",
+                    (GCallback) _keyboard_press_cb, self);
+
+  g_signal_connect (self->client, "click-event",
+                    (GCallback) _click_cb, self);
+
+  g_signal_connect (self->client, "move-cursor-event",
+                    (GCallback) _move_cursor_cb, self);
+
+  g_signal_connect (self->client, "request-quit-event",
+                    (GCallback) _state_change_cb, self);
+
+  ShellGlobal *global = shell_global_get ();
+  MetaDisplay *display = shell_global_get_display (global);
+
+  g_signal_connect (display, "grab-op-begin",
+                    (GCallback) _grab_op_begin_cb, self);
+
+  g_signal_connect (display, "grab-op-end",
+                    (GCallback) _grab_op_end_cb, self);
+
+
+  self->cursor_tracker =
+    meta_cursor_tracker_get_for_display (display);
+
+  g_signal_connect (self->cursor_tracker, "cursor-changed",
+                    (GCallback) _cursor_changed_cb, self);
+}
+
+static void
+_disconnect_signals (ShellVRMirror *self)
+{
+  g_signal_handlers_disconnect_matched (
+    self->client, G_SIGNAL_MATCH_FUNC, 0, 0, NULL,
+    _keyboard_press_cb, NULL);
+  g_signal_handlers_disconnect_matched (
+    self->client, G_SIGNAL_MATCH_FUNC, 0, 0, NULL,
+    _click_cb, NULL);
+  g_signal_handlers_disconnect_matched (
+    self->client, G_SIGNAL_MATCH_FUNC, 0, 0, NULL,
+    _move_cursor_cb, NULL);
+  g_signal_handlers_disconnect_matched (
+    self->client, G_SIGNAL_MATCH_FUNC, 0, 0, NULL,
+    _state_change_cb, NULL);
+
+  ShellGlobal *global = shell_global_get ();
+  MetaDisplay *display = shell_global_get_display (global);
+
+  g_signal_handlers_disconnect_matched (
+    display, G_SIGNAL_MATCH_FUNC, 0, 0, NULL,
+    _grab_op_begin_cb, NULL);
+
+  g_signal_handlers_disconnect_matched (
+    display, G_SIGNAL_MATCH_FUNC, 0, 0, NULL,
+    _grab_op_end_cb, NULL);
+
+  g_signal_handlers_disconnect_matched (
+    self->cursor_tracker, G_SIGNAL_MATCH_FUNC, 0, 0, NULL,
+    _cursor_changed_cb, NULL);
+}
+
+
+void
+shell_vr_mirror_initialize (ShellVRMirror *self)
+{
+  self->client = xrd_shell_new ();
+  if (!self->client)
+    {
+      g_print ("Failed to initialize xrdesktop!\n");
+      g_print ("Usually this is caused by a problem with the VR runtime.\n");
+
+      g_clear_object (&shell_vr_mirror_instance);
+
+      return;
+    }
+
+  g_print ("== Started xrdesktop ==\n");
+
+  self->cursor_gl_texture = 0;
+
+  if (!_load_gl_functions ())
+    g_printerr ("Error: Could not load GL function pointers"
+                " for external memory upload method.\n");
+
+  self->nvidia = g_strcmp0 (gl_get_vendor (), "NVIDIA Corporation") == 0;
+
+  self->grabbed_windows = NULL;
+
+  _init_input (self);
+
+  _mirror_current_windows (self);
+  _arrange_windows_by_desktop_position (self);
+
+
+
+  _connect_signals (self);
+}
+
+/*
+ * Menus, ContextMenus, etc. that should be fixed to a parent window and may
+ * not be individually moved
+ */
+
+static void
+_get_offset (MetaWindow *parent, MetaWindow *child, graphene_point_t *offset)
+{
+  MetaRectangle parent_rect;
+  meta_window_get_buffer_rect (parent, &parent_rect);
+
+  MetaRectangle child_rect;
+  meta_window_get_buffer_rect (child, &child_rect);
+
+  int parent_center_x = parent_rect.x + parent_rect.width / 2;
+  int parent_center_y = parent_rect.y + parent_rect.height / 2;
+
+  int child_center_x = child_rect.x + child_rect.width / 2;
+  int child_center_y = child_rect.y + child_rect.height / 2;
+
+  int offset_x = child_center_x - parent_center_x;
+  int offset_y = child_center_y - parent_center_y;
+
+  offset->x = offset_x;
+  offset->y = - offset_y;
+
+  g_print ("child at %d,%d to parent at %d,%d, offset %d,%d\n",
+           child_center_x, child_center_y,
+           parent_center_x, parent_center_y,
+           offset_x, offset_y);
+}
+
+static gboolean
+_is_child_window (MetaWindow *meta_win)
+{
+  MetaWindowType t = meta_window_get_window_type (meta_win);
+  return
+    t == META_WINDOW_POPUP_MENU ||
+    t == META_WINDOW_DROPDOWN_MENU ||
+    t == META_WINDOW_TOOLTIP ||
+    t == META_WINDOW_MODAL_DIALOG ||
+    t == META_WINDOW_COMBO;
+}
+
+static XrdWindow*
+_get_valid_xrd_parent (ShellVRMirror *self,
+                       MetaWindow    *meta_parent)
+{
+  XrdWindow *xrd_parent = NULL;
+  if (meta_parent != NULL && !_is_excluded_from_mirroring (meta_parent))
+    xrd_parent = xrd_shell_lookup_window (self->client, meta_parent);
+  return xrd_parent;
+}
+
+static bool
+_find_valid_parent (ShellVRMirror *self,
+                    MetaWindow    *child,
+                    MetaWindow   **meta_parent,
+                    XrdWindow    **xrd_parent)
+{
+  /* Try transient first */
+  *meta_parent = meta_window_get_transient_for (child);
+  *xrd_parent = _get_valid_xrd_parent (self, *meta_parent);
+  if (*xrd_parent != NULL)
+    return TRUE;
+
+  /* If this doesn't work out try the root ancestor */
+  *meta_parent = meta_window_find_root_ancestor (child);
+  *xrd_parent = _get_valid_xrd_parent (self, *meta_parent);
+  if (*xrd_parent != NULL)
+    return TRUE;
+
+  /* Last try, check if anything is focused and make that our parent */
+  ShellGlobal *global = shell_global_get ();
+  MetaDisplay *display = shell_global_get_display (global);
+  *meta_parent = meta_display_get_focus_window (display);
+  *xrd_parent = _get_valid_xrd_parent (self, *meta_parent);
+  if (*xrd_parent != NULL)
+    return TRUE;
+
+  /* Didn't find anything */
+  const gchar *title = meta_window_get_title (child);
+  g_warning ("Could not find a parent for `%s`", title);
+
+  return FALSE;
+
+}
+
+gboolean
+shell_vr_mirror_map_actor (ShellVRMirror   *self,
+                           MetaWindowActor *actor)
+{
+  if (!shell_vr_mirror_instance)
+    return FALSE;
+
+  MetaWindow *meta_win = _get_validated_window (actor);
+  if (_is_excluded_from_mirroring (meta_win))
+    return FALSE;
+
+  MetaRectangle rect;
+  meta_window_get_buffer_rect (meta_win, &rect);
+
+  gboolean is_child = _is_child_window (meta_win);
+  XrdWindow *xrd_parent = NULL;
+  MetaWindow *meta_parent = NULL;
+
+  if (is_child)
+    {
+      if (_find_valid_parent (self, meta_win, &meta_parent, &xrd_parent))
+        {
+          // TODO: Port this to new XrdWindow API.
+          // xrdesktop commit 9354945fa1c94386a72b4ca159d40ebcb09437df
+        }
+    }
+
+  const gchar *title = meta_window_get_title (meta_win);
+  const gchar *description = meta_window_get_description (meta_win);
+  g_print ("Map window %p: %s (%s)\n", actor, title, description);
+
+  G3kContext *g3k = xrd_shell_get_g3k (self->client);
+
+  ShellVRWindow *shell_win = g_malloc (sizeof (ShellVRWindow));
+  shell_win->meta_window_actor = actor;
+  shell_win->gl_texture = 0;
+
+  float width_meters = (float)rect.width / (float) SHELL_VR_PIXELS_PER_METER;
+
+  XrdWindow *xrd_win = xrd_window_new (g3k, title, shell_win, rect.width,
+                                       rect.height, width_meters);
+
+  gboolean draggable = !(is_child && meta_parent != NULL && xrd_parent != NULL);
+  xrd_shell_add_window (self->client, xrd_win, G3K_OBJECT (xrd_parent),
+                        draggable, meta_win);
+
+  if (is_child && !draggable)
+    {
+      graphene_point_t offset;
+      _get_offset (meta_parent, meta_win, &offset);
+
+      graphene_point3d_t offset_point = {
+        .x = offset.x,
+        .y = offset.y,
+        .z = 0.1
+      };
+      struct G3kPose offset_pose = g3k_pose_create (&offset_point, NULL);
+
+      g3k_object_set_local_pose (G3K_OBJECT (xrd_win), &offset_pose);
+
+      xrd_window_add_child (xrd_parent, xrd_win);
+    }
+  else if (is_child && xrd_parent == NULL)
+    g_warning ("Can't add window '%s' as child. No parent candidate!\n", title);
+
+  if (!is_child)
+    {
+      _apply_desktop_position (meta_win, xrd_win, self->top_layer);
+      self->top_layer++;
+    }
+
+  ShellVREffect *effect = g_object_new (SHELL_TYPE_VR_EFFECT, NULL);
+  effect->xrd_win = xrd_win;
+
+  clutter_actor_meta_set_name (CLUTTER_ACTOR_META (effect), title);
+
+  clutter_actor_add_effect (CLUTTER_ACTOR(actor),
+                            CLUTTER_EFFECT(effect));
+
+  return TRUE;
+}
+
+gboolean
+shell_vr_mirror_destroy_actor (ShellVRMirror   *self,
+                               MetaWindowActor *actor)
+{
+  if (!shell_vr_mirror_instance)
+    return FALSE;
+
+  MetaWindow *meta_win = _get_validated_window (actor);
+  g_print ("WINDOW CLOSED: %s: %s\n",
+           meta_window_get_wm_class (meta_win),
+           meta_window_get_title (meta_win));
+
+
+  XrdWindow *xrd_win = _meta_win_to_xrd_window (self, meta_win);
+
+  if (xrd_win)
+    {
+      ShellVRWindow *shell_win;
+      g_object_get (xrd_win, "native", &shell_win, NULL);
+      if (!shell_win)
+        return FALSE;
+
+      // TODO: Removing one effect does not work. This could cause problems
+      // if there are other effects than ours on the window actor.
+      // The meta of type 'ShellVREffect' with name 'Name' is not attached
+      // to the actor 'MetaWindowActorX11'
+      //clutter_actor_remove_effect (CLUTTER_ACTOR(actor),
+      //                             CLUTTER_EFFECT(shell_win->effect));
+
+      clutter_actor_clear_effects (CLUTTER_ACTOR(shell_win->meta_window_actor));
+
+      if (shell_win->gl_texture != 0)
+        _glDeleteTextures (1, &shell_win->gl_texture);
+
+      xrd_shell_remove_window (self->client, xrd_win);
+
+      xrd_window_close (xrd_win);
+
+      g_object_unref (xrd_win);
+      g_free (shell_win);
+    }
+  else
+    {
+      g_printerr ("Could not destroy null xrd win.\n");
+      return FALSE;
+    }
+
+  return TRUE;
+}
+
+gboolean
+shell_vr_mirror_actor_size_changed (ShellVRMirror   *self,
+                                    MetaWindowActor *actor)
+{
+  if (!shell_vr_mirror_instance)
+    return FALSE;
+
+  MetaWindow *meta_win = _get_validated_window (actor);
+
+  MetaRectangle rect;
+  meta_window_get_buffer_rect (meta_win, &rect);
+  g_print ("Window Size Changed: %s: [%d,%d] %dx%d\n",
+           meta_window_get_title (meta_win),
+           rect.x, rect.y, rect.width, rect.height);
+
+  XrdWindow *xrd_win = _meta_win_to_xrd_window (self, meta_win);
+  if (xrd_win == NULL || rect.width < 10 || rect.height < 10)
+    return FALSE;
+
+  // TODO?
+
+  return TRUE;
+}
+
+static void
+shell_vr_mirror_finalize (GObject *object)
+{
+  ShellVRMirror *self = SHELL_VR_MIRROR (object);
+
+  g_print ("== Disabling xrdesktop ==\n");
+  if (self->vr_input)
+    g_object_unref (self->vr_input);
+  if (self->client)
+    g_object_unref (self->client);
+
+  g_slist_free (self->grabbed_windows);
+
+  G_OBJECT_CLASS (shell_vr_mirror_parent_class)->finalize (object);
+}
+
+static void
+shell_vr_mirror_class_init (ShellVRMirrorClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+  gobject_class->finalize = shell_vr_mirror_finalize;
+}
+
+static void
+_destroy_vr_window (XrdWindow     *xrd_win,
+                    ShellVRMirror *self)
+{
+  ShellVRWindow *shell_win;
+  g_object_get (xrd_win, "native", &shell_win, NULL);
+  if (shell_win)
+    shell_vr_mirror_destroy_actor (self, shell_win->meta_window_actor);
+}
+
+void
+shell_vr_mirror_destroy_instance (void)
+{
+  ShellVRMirror *self = shell_vr_mirror_instance;
+  if (!self)
+    {
+      g_printerr ("No ShellVRMirror instance to destroy!\n");
+      return;
+    }
+
+  _disconnect_signals (self);
+
+  self->shutdown = TRUE;
+
+  /*
+   * We have to clean up windows first because it will only clean up as long
+   * as there is an active shell_vr_mirror_instance
+   */
+  GSList *windows = xrd_shell_get_windows (self->client);
+  g_slist_foreach (windows, (GFunc) _destroy_vr_window, self);
+
+  /* Let the outside world know that the single instance is gone. */
+  shell_vr_mirror_instance = NULL;
+
+  g_object_unref (self);
+}
+
diff --git a/src/shell-vr-mirror.h b/src/shell-vr-mirror.h
new file mode 100644
index 000000000..9480e8343
--- /dev/null
+++ b/src/shell-vr-mirror.h
@@ -0,0 +1,52 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+#ifndef __SHELL_VR_MIRROR_H__
+#define __SHELL_VR_MIRROR_H__
+
+#include <clutter/clutter.h>
+#include "st.h"
+
+#include "shell-global.h"
+#include <xrd.h>
+
+/* TODO: generate enum header with gschemas */
+typedef enum {
+  UPLOAD_METHOD_VK_SHARED_GL_MEMORY = 1,
+  UPLOAD_METHOD_VK_UPLOAD_RAW
+} UploadMethod;
+
+G_BEGIN_DECLS
+
+#define SHELL_TYPE_VR_MIRROR (shell_vr_mirror_get_type ())
+G_DECLARE_FINAL_TYPE (ShellVRMirror, shell_vr_mirror,
+                      SHELL, VR_MIRROR, GObject)
+
+#define SHELL_TYPE_VR_EFFECT (shell_vr_effect_get_type ())
+G_DECLARE_FINAL_TYPE (ShellVREffect, shell_vr_effect,
+                      SHELL, VR_EFFECT, ClutterEffect)
+
+ShellVRMirror *shell_vr_mirror_new          (void);
+ShellVRMirror *shell_vr_mirror_get_instance (void);
+ShellVRMirror *shell_vr_mirror_create_instance (void);
+void shell_vr_mirror_destroy_instance (void);
+
+void shell_vr_mirror_initialize (ShellVRMirror *self);
+
+gboolean
+shell_vr_mirror_map_actor (ShellVRMirror   *self,
+                           MetaWindowActor *actor);
+
+gboolean
+shell_vr_mirror_destroy_actor (ShellVRMirror   *self,
+                               MetaWindowActor *actor);
+
+gboolean
+shell_vr_mirror_actor_size_changed (ShellVRMirror   *self,
+                                    MetaWindowActor *actor);
+
+void
+shell_dbus_xr_init (void);
+
+G_END_DECLS
+
+#endif /* __SHELL_VR_MIRROR_H__ */
diff --git a/src/shell-wm.c b/src/shell-wm.c
index a11e41904..e06b531b0 100644
--- a/src/shell-wm.c
+++ b/src/shell-wm.c
@@ -6,9 +6,11 @@
 
 #include <meta/meta-enum-types.h>
 #include <meta/keybindings.h>
+#include <meta/meta-shaped-texture.h>
 
 #include "shell-wm-private.h"
 #include "shell-global.h"
+#include "shell-vr-mirror.h"
 
 struct _ShellWM {
   GObject parent;
@@ -273,6 +275,19 @@ void
 shell_wm_completed_map (ShellWM         *wm,
                         MetaWindowActor *actor)
 {
+  ShellVRMirror *vr_mirror = shell_vr_mirror_get_instance ();
+  if (vr_mirror)
+    shell_vr_mirror_map_actor (vr_mirror, actor);
+
+  MetaWindow *window = meta_window_actor_get_meta_window (actor);
+  g_print ("shell_wm_completed_map: %s: %s\n",
+           meta_window_get_wm_class (window),
+           meta_window_get_title (window));
+
+  MetaRectangle rect;
+  meta_window_get_buffer_rect (window, &rect);
+  g_print ("[%d,%d] %dx%d\n", rect.x, rect.y, rect.width, rect.height);
+
   meta_plugin_map_completed (wm->plugin, actor);
 }
 
@@ -287,6 +302,10 @@ void
 shell_wm_completed_destroy (ShellWM         *wm,
                             MetaWindowActor *actor)
 {
+  g_print ("shell_wm_completed_destroy\n");
+  ShellVRMirror *vr_mirror = shell_vr_mirror_get_instance ();
+  if (vr_mirror)
+    shell_vr_mirror_destroy_actor (vr_mirror, actor);
   meta_plugin_destroy_completed (wm->plugin, actor);
 }
 
@@ -376,6 +395,10 @@ void
 _shell_wm_size_changed (ShellWM         *wm,
                         MetaWindowActor *actor)
 {
+  ShellVRMirror *vr_mirror = shell_vr_mirror_get_instance ();
+  if (vr_mirror)
+    shell_vr_mirror_actor_size_changed (vr_mirror, actor);
+
   g_signal_emit (wm, shell_wm_signals[SIZE_CHANGED], 0, actor);
 }
 
diff --git a/tools/gnome-shell-set-vr.py b/tools/gnome-shell-set-vr.py
new file mode 100755
index 000000000..bfeffef8f
--- /dev/null
+++ b/tools/gnome-shell-set-vr.py
@@ -0,0 +1,47 @@
+#!/usr/bin/env python3
+
+available_methods = [ "enable", "disable" ]
+
+import sys
+if len(sys.argv) < 2 or (not sys.argv[1] in available_methods):
+    print("Usage: {} [enable|disable]".format(sys.argv[0]))
+    sys.exit()
+method = sys.argv[1]
+
+from gi.repository import Gio, GLib
+
+bus = Gio.bus_get_sync(Gio.BusType.SESSION, None)
+proxy = Gio.DBusProxy.new_sync(bus,
+                               Gio.DBusProxyFlags.DO_NOT_AUTO_START,
+                               None,
+                               'org.gnome.Shell.XR',
+                               '/org/gnome/Shell/XR',
+                               'org.gnome.Shell.XR',
+                               None)
+
+is_enabled = proxy.get_cached_property("enabled")
+
+# print("Is:", is_enabled)
+
+prop_proxy = Gio.DBusProxy.new_sync(bus,
+                                    Gio.DBusProxyFlags.DO_NOT_AUTO_START,
+                                    None,
+                                    'org.gnome.Shell.XR',
+                                    '/org/gnome/Shell/XR',
+                                    'org.freedesktop.DBus.Properties',
+                                    None)
+
+to_set = method == "enable"
+
+# print("Set to:", to_set)
+
+set_variant = GLib.Variant('(ssv)',("org.gnome.Shell.XR", "enabled",
+                           GLib.Variant.new_boolean(to_set)))
+
+prop_proxy.call_sync("Set", set_variant, Gio.DBusCallFlags.NONE, -1, None)
+
+get_variant = GLib.Variant('(ss)',("org.gnome.Shell.XR", "enabled"))
+
+res = prop_proxy.call_sync("Get", get_variant, Gio.DBusCallFlags.NONE, -1, None)
+
+# print ("Is:", res)
diff --git a/tools/meson.build b/tools/meson.build
index d8e217cc4..9230c5a19 100644
--- a/tools/meson.build
+++ b/tools/meson.build
@@ -2,3 +2,9 @@ install_data('gnome-shell-overrides-migration.sh',
   install_dir: libexecdir,
   install_mode: 'rwxr-xr-x'
 )
+
+install_data('gnome-shell-set-vr.py',
+  rename : ['gnome-shell-set-vr'],
+  install_dir: 'bin',
+  install_mode: 'rwxr-xr-x'
+ )
\ No newline at end of file
openSUSE Build Service is sponsored by